目录
- 1. 什么是线程
- 2. 创建线程
- 3. 线程等待
- 3.1 pthread_join函数
- 3.2 线程分离
- 3.2 线程终止的方案
- 4. 线程ID
1. 什么是线程
Linux中没有专门为线程设计TCB,而是用进程的PCB来模拟进程。 这也是为什么有种观点会说Linux下没有真正意义上的线程。
对于线程来说,只创建task_struct,共享同一个地址空间,当前进程的资源(代码+数据),划分为若干份,让每个PCB使用。
线程可以认为是进程里的一个执行流(每一个PCB),由于线程的创建并不需要像进程那样创建数据结构、地址空间等,而是复用进程的PCB等资源,所以线程也被看作是进程的一部分。
有两个要点:
- 线程在进程的地址空间内运行
- CPU调度的时候,只看PCB,每一个PCB已经被分配指向的数据和方法,CPU可以直接调度
这样的设计有一个很大的优点:不用维护复杂的进程和线程的关系,不用单独为线程设计任何算法,直接使用进程的一套相关的方法。OS只需要聚焦在线程间的资源分配上就行了。
所以很明显,创建进程的成本比创建线程大的多。
在windows系统中,采用的是和进程类似的方法,也就是线程自己重新创建一份TCB等结构,可以知道其源码肯定非常复杂。
2. 创建线程
线程创建函数:pthread_create
参数:
thread:返回线程ID
attr:设置线程的属性,attr为NULL表示使用默认属性
start_routine:是个函数地址,线程启动后要执行的函数
arg:传给线程启动函数的参数
返回值:成功返回0;失败返回错误码
编写一个函数来验证它们是否处于同一个进程(由于使用的是第三方库,编译时需要-lpthread链接):
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
void* thread_run(void* args)
{
const char* id = (const char*)args;
while(1){
printf("我是%s线程:%d\n",id,getpid());
sleep(1);
}
}
int main()
{
pthread_t tid;
pthread_create(&tid,NULL,thread_run,(void*)"thread 1");
while(1){
printf("我是main线程:%d\n",getpid());
sleep(1);
}
return 0;
}
运行结果:
首先,两个死循环都能跑起来,如果仅仅只有一个线程,这是根本不可能实现的,说明此时创建了新的线程
其次,可以看见他们的pid都是一样的,说明它们是同一个进程
最后,当收到二号信号的时候,两个线程都直接退出,说明确实是只有一个进程,并且两个线程是同一个进程。
ps -aL是查看线程资源的指令:
这里的LWP其实就是线程的标识符,相当于进程的PID。6548其实就是此时的主线程,6549就是另一个线程。
操作系统调度线程的时候,看的其实是LWP,而不是PID。
也可以批量创建线程:
void* thread_run(void* args)
{}
int main()
{
pthread_t tid[5];
for(int i = 0;i < 5;i++){
pthread_create(tid+i,NULL,thread_run,(void*)"thread 1");
}
return 0;
}
运行结果:
可以看见LWP刚好有六个属于test程序的线程,第一个LWP=PID的是主线程,其他的五个都是按照顺序生成的子线程。
线程健壮性不强,在一个进程下的线程,如果有一个崩溃了,其他的也会随之崩溃。
3. 线程等待
3.1 pthread_join函数
和进程一样,线程也是需要等待的,否则就会造成类似于“僵尸进程”的问题。
线程等待函数pthread_join:
参数:
thread表示需要等待的线程id;
retval是一个输出型参数,用来获取新线程退出的时候,函数的返回值。由于新线程创建的时候函数返回值是void*,所以要将该返回值输出,就要用二级指针void**。
返回值:成功返回0,失败返回错误码。
下列程序可以显示出,主线程确实是可以等待其他线程,并拿到其返回值:
void* thread_run(void* args)
{
const char* id = (const char*)args;
while(1){
printf("我是%s线程:%d\n",id,getpid());
sleep(1);
break;
}
return (void*)11;//随意设置返回值
}
int main()
{
pthread_t tid;
pthread_create(&tid,NULL,thread_run,(void*)"thread 1");
等待
void* status = NULL;
pthread_join(tid,&status);
printf("返回值:%d\n",(int)status);
return 0;
}
运行结果:
可以看到status中确实保存了该新线程的返回值。
值得注意的是,当线程异常的时候,该函数并不会进行处理,因为线程异常已经是属于进程层面的问题了。
3.2 线程分离
如果一个线程不需要被等待,可以将其分离:
分离后的线程不能被等待,主线程v不退出,新线程运行完毕之后会自动释放资源。
3.2 线程终止的方案
-
return:main函数中return代表主线程退出,在其他线程中return代表当前线程退出。
-
新线程通过pthread_exit终止自己,但是不能用exit,因为那是用来终止进程的,除非你想直接终止该进程。
参数是退出码,强转成void*。
- 取消目标线程,直接通过线程id取消线程
4. 线程ID
我们所打印的线程ID其实是pthread库在地址空间当中映射之后的地址,该地址能快速地找到对应线程的相关属性,该地址与LWP是一一对应的关系。