一个程序在运行时,会被加载到内存成了进程。如果一个进程需要做很多的事情,那么为了提高进程的工作效率,会找一些"小弟"来替代进程做一些事情,这个"小弟"就是线程。
进程在创建的时候需要创建资源、创建PCB,所以进程也就是承担分配资源的实体,而线程就是进程给线程分配的资源,让线程去执行。
由此我们可以区别一下进程与线程:进程拥有资源,并且拥有分配资源的能力,是操作系统资源分配的基本单位。而线程仅仅是用着进程所分配的资源给进程做事情,线程是CPU调度的基本单位。
线程
线程的概念
- Linux下没有真正的线程,线程是进程中的一条执行流。线程是内核执行的最小单位,linux下的线程都是用进程模拟的,所以linux下的线程又叫做轻量级进程。
- 一个进程至少有一个执行线程。而同一个进程的多个线程共享同一地址空间,因此同一个进程的所有线程共享数据段和代码,但每个线程都有自己的栈空间,因此线程间通信就非常简单。
- 线程间共享的资源:文件描述符、信号的处理方式、用 户id和组id、堆空间和代码段等。 线程私有的资源:栈空间、上下文、线程id、信号屏 蔽字,调度优先级等。
线程的共享与独有
共享:虚拟地址空间、文件状态表、信号处理方式 、用户id和组id、当前工作目录
独有(相对独有,因为独有的数据其实还是在共享的虚拟地址空间里): 栈区、上下文数据、线程id、errno、信号屏蔽字。
线程和进程
- 线程的相比于进程的优缺点:
优点:线程的创建和销毁成本更低;
线程的调度切换成本更低;
线程间的通信更加方便;
线程的执行粒度更加细致;
缺点:缺乏访问控制---->线程安全;
有些系统调用和程序异常是针对整个进程产生影响;
多个进程对临界(公共)资源进行操作有可能造成数据混乱;
- 线程与进程的区别与联系
<1>进程是操作系统资源分配的基本单位,线程是CPU调度的基本单位。
<2>进程间相互独立,而同一进程中的线程则共用进程的虚拟地址空间(共享堆空间、数据段和代码段)。
<3>进程因为独立性所以健壮性比较强,但通信麻烦,资源成本高;线程间通信非常方便,资源成本低,但缺乏访问控制,需要更多考虑线程的安全性。
多线程/多进程
io密集型程序----->分摊等待
cpu密集型程序--->分摊计算
线程控制
操作系统并没有提供相对应的系统调用来实现线程控制的接口,所以有人就为线程的控制封装了一套线程库(POSIX线程库),使用这套接口创建的线程称之为用户态的线程,但是它在操作系统内部对应了一个轻量级进程(pcb)。
线程ID
在Linux下引入了线程组,多线程的进程被称作线程组。 线程组内每一个线程都拥有自己的进程描述符,在自己的进程描述符内部的PID,其实是这个线程的线程ID,而其内部有一个TGID,含义为Thread Group ID这才是真正我们所看到的PID。也就是说其实我们看到的PID实际上是线程组ID。而所有线程组内部的线程,它们的PID是不同的,但是它们的TGID肯定相同。我们把它们共同的这个TGID称作进程PID。
在线程组中,线程组的ID取决于主线程的线程ID,而我们所说的进程PID其实就是主线程的线程ID。 我们可以用gettid()来获取线程的线程ID
获取线程自身ID:pthread_self(void),它获得的其实是我们在创建线程时所传入的tid,相当于一个标识符,这个标识符能够让线程库更好的对线程进行操作等。而并不是我们想要的线程ID。
使用pthread_create函数时会产生一个线程ID,存放在第一个参数指向的地址中,这个线程ID并不是我们理解的线程ID,这个线程ID是线程库为了方便操作这些线程而使用的ID。而我们说的线程ID是进程调度范畴的,线程是轻量级进程,是调度器调度的基本单位,所以需要一个唯一的ID与之对应,这样可以更好的去调度。
POSIX线程库
线程的所有调用函数都不是系统调用,并且在链接是需要手动链接线程库-lphread:
- 线程创建:pthread_create (&tid, NULL, thr_start, NULL)
int pthread_create(pthread_t* thread, const pthread_attr_t* attr, void*(*start_routine)(void*), void* arg);
//功能:创建一个线程
//参数:
//thread:返回线程ID
//attr:设置线程属性,attr为NULL表示使用默认属性
//start_routine:函数地址,表示要执行的函数
//arg:给线程要执行的函数传参数
//返回值:
//成功返回0,失败返回错误码
- 通过参数返回一个用户态的线程id----->这个线程id是线程地址空间在进程虚拟地址空间中的首地址;
- 这个用户态线程的操作都是围绕这个用户态的线程id来操作的;
- 每一个线程都是一个task_struct,也就意味着每个线程都有一个pid,但是ps命令只能显示一个,task_struct中不仅有pid还有一个tgid(线程组id 等于 线程组领导者的pid),那么ps看到的pid实际上是这个tgid(线程组id), 因此Linux下的进程就成了线程组;
- pthread_self这个接口用于获取线程自身id---->tid,tid同时也是该线程的首地址,通过tid可以找到该线程;
- 查看线程信息可以使用ps -L命令, LWP这一列显示的就是线程pcb中的pid。
- 线程终止:pthread_exit / pthread_cancel
#include <pthread.h>
void pthread_exit(void* value_ptr);
//功能:线程终止
//参数:
//value_ptr:value_ptr不要指向一个局部变量
//返回值:无返回值
int pthread_cancel(pthread_t thread)
//功能:取消一个执行的线程
//参数:
//thread:线程ID
//返回值:成功返回0,失败返回错误码
- 不能在main中return,不能调用exit,因为这两个都是退出进程的,进程一退出所有的线程也就退出了。
- pthread_exit :谁调用谁退出 (退出自己)
- pthread_cancel:取消其他线程,让其他线程退出 (退出别人)
- 线程等待:pthrea_join 获取其他普通线程的退出返回值,并且允许操作系统回收线程资源,避免产生僵尸线程
#include <pthread.h>
int pthread_join(pthread_t thread, void** value_ptr);
//功能:等待线程结束
//参数:
//thread:线程ID
//value_ptr:输出型参数,指向一个指针,这个指针指向线程的返回值
//返回值:成功返回0,失败返回错误码
- 线程只有在处于joinable状态(线程默认属性)时才能被等待。
- 如果线程是被取消而退出的,那么线程的返回值为-1.(pthread_cancel)
- 线程分离:pthread_detach 分离一个线程,当线程退出后直接释放资源,无法获取返回值,不能被等待。
int pthread_detach(pthread_t pthread)//分离线程
- 设置线程的属性为分离属性(detach),退出后直接释放资源,处于分离状态的线程是不能被等待的。
- 只有在不关心线程的退出返回值时才会设置detach,因为一旦设置就无法获取返回值(无法pthread_join)。