线程概念
什么是线程?
- 在一个程序里的一个执行流叫做线程。
- 一切进程至少有一个线程
- 线程在进程内部运行,本质是在进程地址空间内运行
- 在Linux系统中,在CPU眼中,看到的PCB都要比传统的进程更加轻量化
我们都知道在每一个进程都有属于自己的PCB,里面装满了
描述进程的各种字段…,而线程呢,是在进程中产生的,所以会共享共一个进程地址空间,如上图所示。
线程的优点
- 创建一个新线程的代价要比创建一个新进程小
- 与进程之间的切换相比,线程之间的切换需要操作系统做的工作要小很多
- 线程占比的资源比进程小很多、
- 能充分利用多处理器的可并行数量
- 在等待慢速I/O操作结束的同时,程序可执行其他的计算任务
线程的缺点
- 性能的损失
- 一个很少被外部事件阻塞的计算密集型线程往往无法与共它线程共享同一个处理器。如果计算密集型线程的数量比可用的处理器多,那么可能会有较大的性能损失,这里的性能损失指的是增加了额外的
同步和调度开销,而可用的资源不变。
- 一个很少被外部事件阻塞的计算密集型线程往往无法与共它线程共享同一个处理器。如果计算密集型线程的数量比可用的处理器多,那么可能会有较大的性能损失,这里的性能损失指的是增加了额外的
- 健壮性差: 编写多线程的时候,很多时候线程是不安全的
进程和线程的差别
- 进程是资源分配的基本单位,站在内核的角度来讲,进程就是承担分配系统资源的基本实体
- 线程是调度的基本单位
- 线程共享进程数据,但也有自己的一部分数据
- 线程ID
- 一组寄存器:保存属于自己的上下文
- 栈(这很重要)
- errno
- 信号屏蔽字等等
以下我将聊一聊POSIX线程库
- 与线程相关的函数构成了一个完整的系列,绝大多都是“pthread_”打头
- 所以要用这些函数需要引用<pthraed.h>头文件
- 链接的时候需要加上-lpthread
在我们链接线程库的时候,我们需要将线程库给加载进自己的地址空间中(共享区),然后当我们需要什么函数的时候,直接去共享区去取。
创建线程
第一个参数(thread):线程的ID
第二个参数(attr):线程的属性
第三个参数(start_routine):是个函数地址,线程启动的时候执行的函数
第四个参数(arg):传给线程启动函数的参数
返回值:失败返回0,否则返回错误码,错误码被设置了
关于线程ID
其实真实的线程ID是下图所示
pthread_t是什么类型呢? 我们可以理解为是进程地址空间的一个地址。
前面讲过,当程序运行的时候,会将线程库给放进共享区中,所以这个线程ID就是共享区中的一个地址。 而这个地址其实就是线程控制块的地址,里面存放着线程独有的数据
你看PID一样,但是LWP是不一样的,这样证明了我上述的结论。
5 void* routine(void *args)
6 {
7 int i;
8 for(;;)
9 {
10 std::cout << "I am thread" <<std::endl;
11 sleep(1);
12 }
13 }
14
15 int main()
16 {
17 pthread_t tid;
18 int ret;
19 if(ret == pthread_create(&tid,nullptr,&routine,nullptr) != 0)
20 {
21 std::cout << "pthread_create err" << std::endl;
22 exit(-1);
23 }
24
25 int i;
26 for(;;)
27 {
28 std::cout << "I am main thread" <<std::endl;
29 sleep(1);
30 }
31
32 return 0;
33 }
线程退出
参数 value_ptr: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参数。