文章目录
- 前言
- 一、POSIX线程库
- 1.概念
- 2.pthread线程库是应用层的原生线程库
- 3.错误的检查
- 二、线程控制
- 1.创建线程——pthread_create
- pthread_create函数
- 例子
- 创建一个新线程
- 主线程创建一批新线程
- 2.获取线程ID——pthread_self
- 3.线程等待——pthread_join
- 4.线程终止——return、pthread_exit、pthread_cancel
- return
- pthread_exit
- pthread_cancel
- 5.分离线程——pthread_detach
- pthread_detach函数
- 例子
- 总结
前言
本文介绍了Linux下的线程控制。
一、POSIX线程库
1.概念
与线程有关的函数构成了一个完整的系列,大多数函数名都是以“pthread_”为开头的,要使用这些函数需要引入头文件pthread.h
。链接这些线程函数库需要使用编译器命令的-lpthread
选项。
2.pthread线程库是应用层的原生线程库
我们在Linux之多线程(上)这篇文章中了解:在Linux中没有真正意义上的线程,因此系统无法直接给我们提供创建线程的系统接口,只能提供创建轻量级进程额度接口。
在用户角度,当我们想创建一个线程时会想使用thread_sreate这样的接口,而不是像vfork这样的函数。用户不能直接访问OS,所以OS在用户和系统调用之间提供了编写好的用户级线程库,这个库一般称为pthread库。任何Linux操作下系统都必须默认携带这个库,因此这个库也称为原生线程库。
原生线程库本质上是对轻量级进程的系统调用(clone)做了封装——pthread_create,用户层也因此模拟实现了一套线程相关的接口。
用户眼中的线程实际上会在OS内部被转化为轻量级进程。
3.错误的检查
传统的函数,成功就返回0,失败返回-1,并且给全局变量errno赋错误码以指示错误。
pthread函数出错时并不会设置全局变量errno(大部分其他POSIX函数会设置),而是讲错误码通过返回值返回。当然,pthread函数是提供了线程内的errno变量,以支持其他使用errno的代码。对于pthread函数的错误,建议通过返回值判定,因为读取返回值比读取线程内的errno变量的开销更小。
二、线程控制
1.创建线程——pthread_create
pthread_create函数
参数:
- thread:获取线程的ID,该参数是输出型参数;
- attr:用于设置创建线程的属性,传入nullptr表示默认,这个属性一般不用管直接传nullptr就行;
- start_routine:函数地址,表示线程启动后要执行的函数;
- arg:传给线程例程的参数。
返回值:
成功返回0,失败返回错误码。
例子
创建一个新线程
文件mythread.cc
1 #include<iostream>
2 #include<string>
3 #include<unistd.h>
4 #include<pthread.h>
5 #include<assert.h>
6 using namespace std;
7 void* thread_routine(void* args)
8 {
9 string name = static_cast<const char*>(args);//安全的进行强制类型转换
10 while(1)
11 {
12 cout<<"这是新线程, name:"<<name<<endl;
13 sleep(1);
14 }
15 }
16 int main()
17 {
18 pthread_t id;
19 int n = pthread_create(&id, nullptr, thread_routine, (void*)"thread new");
20 assert(n == 0);
21 (void)n;
22 while(1)
23 {
24 cout<<"我是主线程,我正在运行"<<endl;
25 sleep(1);
26 }
27 return 0;
28 }
这里编译运行需要注意的是,pthread_create接口是库提供给我们的,我们使用的接口如果不是语言上的接口或者操作系统的接口,而是库提供的接口,那么在编译的时候是无法通过的,需要链接这个库才能编译成功。要链接这个库首先要找到这个库,-L:找到库在哪里;-l:找到头文件在哪里,库已经在系统中安装好了,所以除了高所系统库和头文件在哪里以外,还要知道是链接哪一个库(库名字)。
所以要加上-lpthread
。
此时我们用ps axj命令查看当前进程的信息时,虽然此时该进程中有两个线程,但是我们只能看到一个进程,因为这两个线程是属于一个进程的:
要想查看到轻量级进程需要使用ps -aL指令:
其中LWP(Light Weight Process)表示的是轻量级进程的ID,可以看到显示出的两个轻量级进程的PID是相同的(因为它们属于同一个进程),而每个轻量级进程都有唯一的LWP。
注意:主线程的PID和LWP是相同的,PID和LWP不相同的是新线程,所以CPU进行调度时,是以LWP为标识符进行标定一个线程执行流。
线程一旦被创建,几乎所有的资源都是被所有线程所共享的,因此线程之间想要进行交互是很容易的,因为直接就可以看到同一份资源。
线程要有自己的私有资源:
线程被调度就要有独立的PCB属性——LWP;
线程被切换时正在运行,需要进行上下文的保存,因此线程要有私有的上下文结构;
每个线程都要独立的运行,所以线程要有自己独立额度栈结构。
主线程创建一批新线程
让主线程一次性创建十个新线程,并让创建的每个新线程都去执行start_routine
函数,即start_routine
这个函数会被重复进入。并且start_routine
函数是可重入函数(不会产生二义性),没有因为一个线程去影响另一个线程。在函数定义内定义的变量都是局部变量具有临时性,所以在多线程的情况下也没有问题。
文件mythread.cc
这也说明了每个线程都有自己独立的栈结构。
2.获取线程ID——pthread_self
获取线程ID:1.创建线程时通过输出型参数获取;2.通过pthread_self接口函数获取。
我们可以通过主线程打印出新线程的ID,再通过新线程打印出自己的ID,判断是否相同。
结果是相同的。
3.线程等待——pthread_join
一个线程退出时和进程一样是需要等待的,如果线程不等待,对应的PCB没有被释放也会造成类似僵尸进程的问题(内存泄漏)。所以线程也需要被等待:1.获取新线程的退出信息;2.回收新线程对应的PCB等内核资源,防止内存泄漏。
参数:
thread是被都能打线程的ID;
retval:线程退出时的退出码。
void** retval:输出型参数,主要用于获取线程退出时返回的退出结果。之所以是void**,是因为如果想作为输出型结果返回就必须是void**(因为线程函数的返回结果是void*)
返回值:线程等待成功返回0,等待失败返回错误码。
没有看到线程退出时对应的退出码是因为线程出异常时收到信号,整个进程都会退出,而退出信息需要进程来关心,所以pthread_join默认会认为函数是调用成功的(等待成功),它不会考虑程序出现异常的情况,异常问题是进程该考虑的情况。
4.线程终止——return、pthread_exit、pthread_cancel
一个线程,如果只是想终止该线程而不是整个进程,有三种做法:
- 直接从线程的函数结束,return就可以终止该线程;
- 线程可以自己调用pthread_exit终止自己;
- 一个线程可以调用pthread_cancel来终止同一个进程中的另一个线程。
return
pthread_exit
pthread_cancel
5.分离线程——pthread_detach
线程是可以等待的,等待的时候是join的等待(阻塞式等待)。如果我们不想等待:不去等待线程,而是进行分离线程处理。默认情况下,新创建的线程是joinable的,线程退出后,需要对其进行pthread_join操作,否则无法释放该线程的资源,造成内存泄漏。
如果我们并不关心线程的返回值,此时join对我们来说是一种负担,这时,我们可以告诉OS,当线程退出时,自动释放线程资源,这种策略就是线程分离。
pthread_detach函数
例子
创建新线程,让主线程与新线程运行起来,主线程等待新线程退出,等待完毕返回n。由于我们现在让新线程进行分离,那么按照理论此时主线程的等待结果是失败的。
文件mythread.cc
1 #include<iostream>
2 #include<unistd.h>
3 #include<pthread.h>
4 #include<string.h>
5 using namespace std;
6 #include<string>
7 string changeld(const pthread_t& thread_id)
8 {
9 char tid[128];
10 snprintf(tid, sizeof(tid), "0x%x", thread_id);
11 return tid;
12 }
13 void* start_routine(void* args)
14 {
15 string name = static_cast<const char*>(args);
16 pthread_detach(pthread_self());//线程分离,设置为分离状态
17 int cnt = 5;
18 while(cnt--)
19 {
20 cout<<name<<"is running..."<<changeld(pthread_self())<<endl;
21 sleep(1);
22 }
23 return nullptr;
24 }
25 int main()
26 {
27 pthread_t tid;
28 pthread_create(&tid, nullptr, start_routine, (void*)"thread 1");
29 string main_id = changeld(pthread_self());
30 cout<<"main thread running... new thread id:"<<changeld(tid)<<"main thread id:"<<main_id<<endl;
31 int n = pthread_join(tid, nullptr);
32 cout<<"result:"<<n<<":"<<strerror(n)<<endl;
33 return 0;
34 }
运行:
但是我们发现等待结果依旧是成功的,这是为什么?
因为,我们创建新线程后,并不确定新线程和主线程哪个先被调度,所以可能导致我们还没有执行新线程的pthread_detach时,主线程就去等待新线程了。也就是说,新线程还没有来得及分离自己,主线程就去等待了。
因此我们可以让主线程sleep(2),保证新线程是分离的状态主线程再去等待,则此时等待是失败的。
文件mythread.c
1 #include<iostream>
2 #include<unistd.h>
3 #include<pthread.h>
4 #include<string.h>
5 using namespace std;
6 #include<string>
7 string changeld(const pthread_t& thread_id)
8 {
9 char tid[128];
10 snprintf(tid, sizeof(tid), "0x%x", thread_id);
11 return tid;
12 }
13 void* start_routine(void* args)
14 {
15 string name = static_cast<const char*>(args);
16 pthread_detach(pthread_self());//线程分离,设置为分离状态
17 int cnt = 5;
18 while(cnt--)
19 {
20 cout<<name<<"is running..."<<changeld(pthread_self())<<endl;
21 sleep(1);
22 }
23 return nullptr;
24 }
25 int main()
26 {
27 pthread_t tid;
28 pthread_create(&tid, nullptr, start_routine, (void*)"thread 1");
29 string main_id = changeld(pthread_self());
30 cout<<"main thread running... new thread id:"<<changeld(tid)<<"main thread id:"<<main_id<<endl;
31 sleep(2);
32 int n = pthread_join(tid, nullptr);
33 cout<<"result:"<<n<<":"<<strerror(n)<<endl;
34 return 0;
35 }
运行:
当然,我们也可以直接让主线程直接pthread_detach,而不是让新线程分离:线程运行起来就直接分离了,分离成功就去join了,此时新线程就去等待了。
文件mythread.c
1 #include<iostream>
2 #include<unistd.h>
3 #include<pthread.h>
4 #include<string.h>
5 using namespace std;
6 #include<string>
7 string changeld(const pthread_t& thread_id)
8 {
9 char tid[128];
10 snprintf(tid, sizeof(tid), "0x%x", thread_id);
11 return tid;
12 }
13 void* start_routine(void* args)
14 {
15 string name = static_cast<const char*>(args);
16 int cnt = 5;
17 while(cnt--)
18 {
19 cout<<name<<"is running..."<<changeld(pthread_self())<<endl;
20 sleep(1);
21 }
22 return nullptr;
23 }
24 int main()
25 {
26 pthread_t tid;
27 pthread_create(&tid, nullptr, start_routine, (void*)"thread 1");
28 string main_id = changeld(pthread_self());
29 pthread_detach(tid);//让主线程线程分离
30 cout<<"main thread running... new thread id:"<<changeld(tid)<<"main thread id:"<<main_id<<endl;
31 int n = pthread_join(tid, nullptr);
32 cout<<"result:"<<n<<":"<<strerror(n)<<endl;
33 return 0;
34 }
总结
以上就是今天要讲的内容,本文介绍了线程控制相关的概念。本文作者目前也是正在学习Linux相关的知识,如果文章中的内容有错误或者不严谨的部分,欢迎大家在评论区指出,也欢迎大家在评论区提问、交流。
最后,如果本篇文章对你有所启发的话,希望可以多多支持作者,谢谢大家!