目录
- 一、线程与轻量级进程的关系
- 二、进程创建
- 1.线程创建
- 线程创建函数(pthread)
- 查看和理解线程id
- 主线程与其他线程之间的关系
- 三、线程等待(回收)
- 四、线程退出
- 线程退出情况
- 线程退出方法
- 五、线程分离
- 线程的优点
- 线程的缺点
一、线程与轻量级进程的关系
首先我想问一个问题,Linux中有没有真线程呢? 答案是没有,Linux中只有轻量级进程
但是用户不知道“轻量级进程”,认为只有进程和线程。因此,Linux系统中不会有与线程相关的系统调用,只有轻量级进程的系统调用
Linux系统为了能让用户进行正常的使用Linux线程,Linux设计者在用户与内核之间设计了一个 pthread库—原生线程库
**作用是将轻量级进程的系统调用进行封装,转成线程相关的接口语义提供给用户,让用户感觉自己使用的是线程,但实际上底层是轻量级进程 **,所以我们平时在Linux中使用线程必须带上这个库, 不过要注意的是这个库并不属于LInux内核,只要是库它就是在用户级实现的,因此有许多人将Linux的线程称作“用户级线程”,所有线程的实现都是在 用户级实现的
二、进程创建
1.线程创建
线程创建函数(pthread)
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,失败返回错误码
举例:
#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <sys/types.h>void *newthreadrun(void *args)
{while (true){std::cout << "I am new thread, pid: " << getpid() << std::endl;sleep(1);}
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, newthreadrun, nullptr);while (true){std::cout << "I am main thread, pid: " << getpid() << std::endl;sleep(1);}
}
有个注意事项就是,如果我们创建线程传递的是 类成员函数 ,那么就需要将我们的类成员函数声明为static函数,因为如果类成员函数的参数中默认会有一个this指针,将类成员函数声明为static函数,这个成员函数就没有this指针了,否则就会造成匹配错误
查看和理解线程id
使用 ps -aL
查看
当我们使用 ps -aL
查看进程时我们会发现除了我们熟知的PID,还有一个陌生的LWP
如果这个进程是单进程的话,这个PID和LWP是一样的
如果这个进程是多线程的话,这个LPW代表不同线程(轻量级进程(light weight process))LWP
其中这个PID是主线程的ID
pthread_ create
函数会产生一个线程ID,存放在第一个参数指向的地址中。该线程ID和前面说的线程ID不是一回事,前面讲的线程ID属于进程调度的范畴。因为线程是轻量级进程,是操作系统调度器的最小单位,所以需要一个数值来唯一表示该线程
pthread_ create
函数第一个参数指向一个虚拟内存单元,该内存单元的地址即为新创建线程的线程ID,属于NPTL线程库的范畴。线程库的后续操作,就是根据该线程ID来操作线程的
线程库NPTL提供了pthread_ self
函数,可以获得线程自身的ID:
pthread_t pthread_self(void);
主线程与其他线程之间的关系
- 主线程与其他线程谁先运行?
这个答案是不确定的,由调度器决定
- 主线程退出,其他线程继续运行还是退出?
主线程退出,就等同于这个进程退出了,而进程是承担分配系统资源的基本实体,进程退出了,进程所拥有的内部资源也应该被释放,也包括内部的执行流等,所以只要主线程退出了,所有进程都要退出
- 这也意味着我们的主线程需要最后结束
三、线程等待(回收)
前面我们学过,进程在退出时需要回收它(wait),否则就会变成僵尸进程,而线程同样如此,线程也需要被回收(wait),否则也会造成内存泄漏问题
线程等待函数 :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参数
四、线程退出
线程退出情况
线程退出无非三种情况:
- 代码跑完,结果对
- 代码跑完,结构错
- 代码出异常
重点是出异常情况:
如果在多线程中,任意一个进程出现异常,都会导致整个进程退出,因此有些人说多线程代码往往健壮性不好
线程退出方法
- 使用return
从线程函数return。这种方法对主线程不适用,从main函数return相当于调用exit。
- 使用pthread_exit函数
线程可以调用pthread_ exit终止自己,类似于进程退出的exit
void pthread_exit(void *value_ptr);
无返回值,跟进程一样,线程结束的时候无法返回到它的调用者(自身)
需要注意,pthread_exit或者return返回的指针所指向的内存单元必须是全局的或者是用malloc分配的,不能在线程函数的栈上分配,因为当其它线程得到这个返回指针时线程函数已经退出了
- 使用pthread_cancel函数
取消一个执行中的线程
int pthread_cancel(pthread_t thread);
- thread:线程ID
- 返回值:成功返回0;失败返回错误码
五、线程分离
默认情况下,新创建的线程是joinable的,也就是说,在线程退出后,需要对其进行pthread_join操作,否则无法释放资源,从而造成系统泄漏,导致类似僵尸进程的事件
但如果我们的主线程并不关心其他线程的返回值,join它是一种负担,这个时候,我们可以告诉系统,当该线程退出时,自动释放该线程资源,也就是说,我们可以将这个线程分离出去,可以说使该线程与主线程达到真正的并行
不过所谓的分离只是线程的一种工作状态,并不是说页表、进程地址空间等分离了,底层依旧属于同一个进程,因此当主线程退出时,分离线程还是会跟着退出,只是不需要等待了
线程并行方法:
- 在主线程分离其他线程
int pthread_detach(pthread_t thread);
- 在该线程中分离线程
pthread_detach(pthread_self());
举例:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
void* thread_run(void* arg)
{pthread_detach(pthread_self());printf("%s\n", (char*)arg);return NULL;
}
int main(void)
{pthread_t tid;if (pthread_create(&tid, NULL, thread_run, "thread1 run...") != 0) {printf("create thread error\n");return 1;}int ret = 0;sleep(1);if (pthread_join(tid, NULL) == 0) {printf("pthread wait success\n");ret = 0;}else {printf("pthread wait failed\n");ret = 1;}return ret;
}
线程的优点
- 创建一个新线程的代价要比创建一个新进程小得多
- 与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多
- 线程占用的资源要比进程少很多
- 能充分利用多处理器的可并行数量
- 在等待慢速I/O操作结束的同时,程序可执行其他的计算任务
- 计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现
- I/O密集型应用,为了提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作。
线程的缺点
- 性能损失
一个很少被外部事件阻塞的计算密集型线程往往无法与共它线程共享同一个处理器。如果计算密集型
线程的数量比可用的处理器多,那么可能会有较大的性能损失,这里的性能损失指的是增加了额外的 同步和调度开销,而可用的资源不变。
- 健壮性降低
编写多线程需要更全面更深入的考虑,在一个多线程程序里,因时间分配上的细微偏差或者因共享了
不该共享的变量而造成不良影响的可能性是很大的,换句话说线程之间是缺乏保护的。
- 缺乏访问控制
进程是访问控制的基本粒度,在一个线程中调用某些OS函数会对整个进程造成影响。
- 编程难度提高
编写与调试一个多线程程序比单线程程序困难得多