1.线程与进程的关系
说到线程概念,必须先说明进程。进程是一个运行中的程序,在操作系统中,一个程序运行起来后就会被加载到内存中。操作系统创建了一个进程描述符(PCB)对程序的运行进行描述控制。因此进程就是PCB,在Linux下用task_struct结构体来描述。Linux系统下,用进程PCB来模拟线程,因此Linux下一个线程就是一个轻量级进程。如果说PCB称为了线程,那么进程就是线程组。一个进程中至少有一个或多个线程。
因为Linux下PCB是一个线程,因此线程是CPU调度的基本单位。因为程序运行时候分配资源,进程(线程组)是资源分配的基本单位。进程中的所有线程共用一个虚拟地址空间,因此可以共享进程的代码段和数据段。又因为线程之间数据的共享,所以线程之间进行通信就变得极为简单。
因为每个线程都是PCB,是CPU调度的基本单位,因此线程可以同时运行,但不会造成调用栈混乱,主要是因为每个线程都有自己的独有数据:
- 栈
- 上下文数据
- 信号屏蔽字
- errno
- 线程标识符
既然多进程可以并行处理任务,多线程也可以。其优缺点分析如下(根据具体使用情况):
多线程的优点: 因为线程共享虚拟地址空间,线程间通信简单,线程的创建/销毁成本更低,线程的执行粒度更细。
多线程缺点: 线程缺乏访问控制(可以访问虚拟地址空间中的任何一块数据);但线程的某些错误会导致整个进程退出,即健壮性较低。
注意:线程的数量也并非越多越好,因为过多的话会导致切换频繁,在一定程度上反而会降低性能。
2.线程控制
操作系统并没有提供直接的创建线程的接口(用户创建线程很麻烦),因此前辈们实现了一套线程控制接口供用户使用。因此我们说创建的线程是一个用户态的线程,但是在内核对应有一个轻量级进程实现程序的调度运行。
2.1 线程创建
int pthread_create(pthread_t *thread, pthread_addr_t *arr,void* (*start_routine)(void *), void *arg);
//thread :用于返回创建的线程的ID
//arr : 用于指定的被创建的线程的属性,上面的函数中使用NULL,表示使用默认的属性
//start_routine : 这是一个函数指针,指向线程被创建后要调用的函数
//arg : 用于给线程传递参数,在本例中没有传递参数,所以使用了NULL
2.2 线程终止
如果需要只终止某个线程而不终止整个进程,可以有以下三种方法:
- 从线程函数return。这种方法对主线程不适用,从main函数return相当调用exit.
(在main中return,退出整个进程;在普通入口函数中return,只是退出调用线程) - 线程可以调用pthread_exit来终止自己(退出调用线程)
- pthread_cancel用来取消指定线程,也可以取消自己。
pthread_exit()函数
//功能:线程终止
//原型:
void pthread_exit(void* value_ptr);
//参数:
value_ptr:不能指向一个局部变量
//返回值:无。跟进程一样,线程结束的时候无法返回到它的调用者。
pthread_cancel()函数
//功能:取消一个执行中的线程
//原型:
int pthread_cancel(pthread_t thread);
//参数:
thread: 线程ID
//返回值:成功返回0,失败返回错误码
2.3 线程等待
为什么要进行线程等待呢?简言之,线程退出后可能会产生“僵尸线程”,所以要等待,以免造成资源泄露。换句话说,即:
等待指定线程退出,就是要获取指定线程的返回值,回收线程资源。因为一个线程被创建后,默认有一个属性为joinable,处于这个属性的线程必须被等待,因为线程退出后,不会自动地回收资源,会造成资源泄露。
pthread_join()
//功能:等待线程结束
//原型:
int pthread_join(pthread_t thread, void** value_ptr);
//参数:
thread:线程ID
value_ptr:指向一个指向被连接线程的返回码的指针的指针
//返回值:成功返回0, 失败返回错误码。
调用该函数的线程将挂起等待,直到id为thread的线程终止。thread线程以不同的方法终止,通过pthread_join得到的终止状态是不同的,总结如下:
- 如果thread线程通过return返回,value_ptr所指向的单元里存放的是thread线程函数的返回值。
- 如果thread线程被别的线程调用pthread_cancel异常终止掉,value_ptr所指向的单元里存放的是常熟PTHREAD_CANCELED。
- 如果thread线程是自己调用pthread_exit()终止的,value_ptr所指向的单元存放的是传给pthread_exit()的参数。
- 如果对thread线程的终止状态不感兴趣,可以传NULL给value_ptr参数。
2.4 线程分离
为什么要进行线程分离呢?(通俗点来说,“对谁不关心,就去分离谁,不去等待谁”)。 即:
分离一个线程,将线程的joinable属性修改为detach属性,处于此属性的线程,退出后将自动回收资源。此属性的线程,不能被等待,否则就会报错。
- 默认情况下,新创建的线程是joinable的,线程退出后,需要对其进行pthread_join操作,否则无法释放资源,从而造成资源泄露;
- 如果不关心线程的返回值,join是一种负担,这个时候,我们可以告诉系统,当线程退出时,自动释放线程资源。
//线程组内其他线程对目标线程进行分离
int pthread_detach(pthread_t thread);
//也可以是线程自己分离自己
int pthread_detach(pthread_self());
//pthread_self()函数获得线程自身的ID