Socket编程(五)——多线程
本篇博客应该会是Socket编程系列的最后一篇博客,之后可能会集中精力用于实际应用方面或者Unix高级编程方面。
什么是线程
在之前的博客中介绍过多进程客户端的概念,多进程可以并行地执行多段代码,从而提高IO的效率。但是使用多进程客户端(服务端)用于通信也有一定的不足,比如进程过于占用内存、进程间通信需要特殊的IPC技术、进程的上下文切换会占用大量资源。于是线程的概念就被提出。线程是小于进程的概念,在一个进程的多个进程中,只共享数据区和堆区域,并不共享栈区域,所以大大减小了线程间切换的开销,可以利用数据区和堆交换数据。
线程的创建和运行
创建线程
Unix系列操作系统的线程创建函数如下:
1 |
|
需要注意的是,没有办法向新线程中传递多个形式参数,但可以创建一个新的结构体用于打包传递所有参数。
最开始我有这样疑问:新线程的几个变量间没有关系,创建一个新的结构体是有必要的吗?网上的大神是这么解答的:只要变量都是用于一个新线程的,就不应该认为这几个变量之间没有关系。
等待线程
1 |
|
第一个参数用于指定需要等待的线程ID,第二个参数需要注意,由于创建的线程main函数返回的是void*
类型的参数,所以需要使用一个指向void*
类型参数的指针来接受线程状态。
线程间同步
上面提到了线程间是共享数据区和堆区域的,所以当两个进程并发运行时,难免会出现同时对一个数据进行操作的情况,为了避免一些不在预期之内的事情发生,就必须使用一定的手段来避免。
互斥量
书中使用“卫生间”的例子来形容互斥量,这个比喻非常形象。互斥量就是阻止两个线程同时对一个区域进行访问,这样的区域称为临界区,互斥量可以理解为访问临界区的“锁”。对互斥量的操作函数如下:
1 |
|
后两个的函数的作用不用说也很容易理解,就是对互斥量进行访问和离开的函数。
使用互斥量时需要注意几点,首先是对互斥量访问的过程中难免存在等待的现象,所以会使得执行速度比原来低很多的情况;其次是临界区的大小问题,扩大临界区可能会使得执行时间变得更长,但是可以减少lock函数和unlock函数的调用次数,所以临界区的大小需要根据实际情况来调整;再次是编程时应该尽最大努力避免死锁。
信号量
信号量和互斥量很相似,但是可以做的事情更多。信号量可以理解为线程间相互通知的变量。相关的操作如下:
1 |
|
线程资源的回收
线程资源的回收有两种方法,对应两个函数分别是pthread_join
和pthread_detach
,尝试从字面去理解就是连接和分离。连接指的是该线程会保留当前的状态和资源,当主进程调用pthread_join
函数时就会把状态返回。分离指的是将两个线程的状态分开,线程执行完毕后自动释放资源,可以理解为解耦。
如果不需要等待线程结束或检索其返回值,或者知道该线程将一直运行,那么将其设置为分离线程可能是一个好的选择,因为它可以避免内存泄漏和其他资源泄漏的问题。