Socket编程(五)——多线程

本篇博客应该会是Socket编程系列的最后一篇博客,之后可能会集中精力用于实际应用方面或者Unix高级编程方面。

什么是线程

在之前的博客中介绍过多进程客户端的概念,多进程可以并行地执行多段代码,从而提高IO的效率。但是使用多进程客户端(服务端)用于通信也有一定的不足,比如进程过于占用内存、进程间通信需要特殊的IPC技术、进程的上下文切换会占用大量资源。于是线程的概念就被提出。线程是小于进程的概念,在一个进程的多个进程中,只共享数据区和堆区域,并不共享栈区域,所以大大减小了线程间切换的开销,可以利用数据区和堆交换数据。

线程的创建和运行

创建线程

Unix系列操作系统的线程创建函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
#include <pthread.h>
/*
* thread:用于保存新线程ID的变量地址
* attr:创建线程属性的参数,传递NULL时创建默认属性
* start_routine:新线程执行的函数
* arg: 新函数的参数地址
* 返回0时正常,否则创建失败
*/
int pthread_create(
pthread_t * restrict thread, const pthread_attr_t *restrict attr,
void * (*start_routine)(void*), void * restrict arg
);

需要注意的是,没有办法向新线程中传递多个形式参数,但可以创建一个新的结构体用于打包传递所有参数。

最开始我有这样疑问:新线程的几个变量间没有关系,创建一个新的结构体是有必要的吗?网上的大神是这么解答的:只要变量都是用于一个新线程的,就不应该认为这几个变量之间没有关系。

等待线程

1
2
3
4
5
6
#include <pthread.h>
int pthread_join(phread_t thread, void ** status);

// example:
void *thr_ret;
pthread_join(t_id,&thr_ret);

第一个参数用于指定需要等待的线程ID,第二个参数需要注意,由于创建的线程main函数返回的是void*类型的参数,所以需要使用一个指向void*类型参数的指针来接受线程状态。

线程间同步

上面提到了线程间是共享数据区和堆区域的,所以当两个进程并发运行时,难免会出现同时对一个数据进行操作的情况,为了避免一些不在预期之内的事情发生,就必须使用一定的手段来避免。

互斥量

书中使用“卫生间”的例子来形容互斥量,这个比喻非常形象。互斥量就是阻止两个线程同时对一个区域进行访问,这样的区域称为临界区,互斥量可以理解为访问临界区的“锁”。对互斥量的操作函数如下:

1
2
3
4
5
6
7
8
9
int pthread_mutex_init(pthread_mutex_t * mutex, const pthread_mutexattr_t * attr);
int pthread_mutex_destroy(pthread_mutex_t *mutex);
/*
* mutex:需要创建或者销毁的互斥量的地址
* attr: 需要创建的互斥量属性
*/

int pthread_mutex_lock(pthread_mutex_t * mutex);
int pthread_mutex_unlock(pthread_mutex_t * mutex);

后两个的函数的作用不用说也很容易理解,就是对互斥量进行访问和离开的函数。

使用互斥量时需要注意几点,首先是对互斥量访问的过程中难免存在等待的现象,所以会使得执行速度比原来低很多的情况;其次是临界区的大小问题,扩大临界区可能会使得执行时间变得更长,但是可以减少lock函数和unlock函数的调用次数,所以临界区的大小需要根据实际情况来调整;再次是编程时应该尽最大努力避免死锁。

信号量

信号量和互斥量很相似,但是可以做的事情更多。信号量可以理解为线程间相互通知的变量。相关的操作如下:

1
2
3
4
5
6
7
8
9
10
#include <semaphore.h>
/*
* sem: 信号量的变量地址值
* pshared;可以创多个进程共享的信号量,这里我们不需要,故填0
* value: 信号量的初始值
*/
int sem_init(sem_t *sem,int pshared, unsigned int value);
int sem_destroy(sem_t *sem);
int sem_post(sem_t * sem);
int sem_wait(sem_t * sem);

线程资源的回收

线程资源的回收有两种方法,对应两个函数分别是pthread_joinpthread_detach,尝试从字面去理解就是连接和分离。连接指的是该线程会保留当前的状态和资源,当主进程调用pthread_join函数时就会把状态返回。分离指的是将两个线程的状态分开,线程执行完毕后自动释放资源,可以理解为解耦。

如果不需要等待线程结束或检索其返回值,或者知道该线程将一直运行,那么将其设置为分离线程可能是一个好的选择,因为它可以避免内存泄漏和其他资源泄漏的问题。


Socket编程(五)——多线程
http://zhouhf.top/2023/04/02/Socket编程(五)——多线程/
作者
周洪锋
发布于
2023年4月2日
许可协议