马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
×
代码1:验证join可以去的线程实行完后的退出码/返回值
- #include<iostream>
- #include<unistd.h>
- #include<pthread.h>
- #include<string>
- using namespace std;
- void* routine(void* arg){
- string name = static_cast<const char*>(arg);
- int cnt = 5;
- while(cnt--)
- {
- cout<<"我是子线程,名字:"<<name<<endl;
- sleep(1);
- }
- return (void*)10;
- }
- int main(){
- pthread_t tid;
- pthread_create(&tid,nullptr,routine,(void*)"thread-1");
- int cnt = 5;
- while(cnt--){
- cout<<"我是主线程"<<endl;
- sleep(1);
- }
- void* ret = nullptr;
- pthread_join(tid,&ret);
- cout<<"子进程退出,退出码为:"<<(long long)ret<<endl;
- }
复制代码
1.main函数竣事,代表主线程竣事,也代表历程竣事
2.新线程对应的入口函数,运行竣事,代表当前线程运行竣事。
3.问题:给线程通报的参数和返回值,可以是恣意范例
代码2:30min证实::给线程通报的参数和返回值,可以是恣意范例 ,下面的例子是类范例
- #include<iostream>
- #include<unistd.h>
- #include<pthread.h>
- #include<string>
- using namespace std;
- class Task{
- public:
- Task(int a,int b)
- :_a(a)
- ,_b(b)
- {}
- int Cal(){
- return _a+_b;
- }
- ~Task() {}
- private:
- int _a;
- int _b;
- };
- class Result{
- public:
- Result(int result)
- :_result(result)
- {}
- int getresult(){
- return _result;
- }
- ~Result(){}
- private:
- int _result;
- };
- void* routine(void* arg){
- Task*t = static_cast<Task*>(arg);
- sleep(1);
- Result* r = new Result(t->Cal());
- sleep(1);
- return r;
- }
- int main(){
- pthread_t tid;
- Task* t = new Task(20,10);
- pthread_create(&tid,nullptr,routine,t);
- Result* ret = nullptr;
- pthread_join(tid,(void**)&ret);
- int n = ret->getresult();
- cout<<"子线程的返回值是:"<<n<<endl;
- }
复制代码
线程停止
假如必要只终⽌某个线程⽽不终⽌整个历程,可以有三种⽅法:
1. 从线程函数return。这种⽅法对主线程不适⽤,从main函数return相称于调⽤exit。
2. 线程可以调⽤pthread_exit终⽌⾃⼰。
3. ⼀个线程可以调⽤pthread_cancel终⽌同⼀历程中的另⼀个线程。
pthread_exit:线程停止
- 功能:线程终⽌
- 原型:
- void pthread_exit(void *value_ptr);
-
- 参数:
- value_ptr:value_ptr不要指向⼀个局部变量,就是指的一个变量
- pthread_exit 的参数 value_ptr 是一个指向某个值的指针,该值将作为线程的返回值。这个返回值可以被主线程通过 pthread_join 获取。
- 返回值:
- ⽆返回值,跟进程⼀样,线程结束的时候⽆法返回到它的调⽤者(⾃⾝)
复制代码 和return是等价的。
线程期待 :pthread_join
为什么必要线程期待?
• 已经退出的线程,其空间没有被释放,仍然在历程的地址空间内。用来释放空间
• 创建新的线程不会复⽤刚才退出线程的地址空间。- 功能:等待线程结束
- 原型
- int pthread_join(pthread_t thread, void **value_ptr);
-
- 参数:
- thread:线程ID
- value_ptr:它指向⼀个指针,后者指向线程的返回值
-
- 返回值:成功返回0;失败返回错误码
复制代码 历程以差别的方式停止,pthread_join得到的线程返回值是不一样的
调⽤该函数的线程将挂起期待,直到id为thread的线程终⽌。thread线程以差别的⽅法终⽌,通过 pthread_join得到的终⽌状态是差别的,总结如下:- 1. 如果thread线程通过return返回,
- value_ptr所指向的单元⾥存放的是thread线程函数的返回值。
- 2. 如果thread线程被别的线程调⽤pthread_cancel异常终掉,
- value_ptr所指向的单元⾥存放的是常 数PTHREAD_CANCELED。
- 3. 如果thread线程是⾃⼰调⽤pthread_exit终⽌的,
- value_ptr所指向的单元存放的是传给 pthread_exit的参数。
- 4. 如果对thread线程的终⽌状态不感兴趣,可以传NULL给value_ptr参数。
复制代码 pthread_cancel:取消线程
参数:tid
主线程取消新线程
取消的时候肯定要包管:新线程已经启动
线程被取消,返回的结果是-1
表明:线程假如被取消退出的结果是-1
最保举的还是return,
线程分离 :pthread_detach(tid)
• 默认情况下,新创建的线程是joinable的,线程退出后,必要对其进⾏pthread_join操纵,否则 ⽆法释放资源,从⽽造成系统走漏。
• 假如不关⼼线程的返回值,join是⼀种负担,这个时候,我们可以告诉系统,当线程退出时,⾃ 动释放线程资源。
假如主线程不想再关心新线程,而是当新线程竣事的时候,让他本身释放??- 设置新线程为分离状态
- 技术层面: 线程默认是需要被等待的,joinable。如果不想让主线程等待新线程
- 想让新线程结束之后,自己退出,设置为分离状态(!joinable or detach) // todo
- 理解层面:线程分离,主分离新,新把自己分离。
- 分离的线程,依旧在进程的地址空间中,进程的所有资源,被分离的线程,依旧可以访问,可以操作。
- 主不等待新线程。
- 分离操作
- 如果线程被设置为分离状态,不需要进行join,join会失败!!
复制代码 主线程设置分离
- #include<iostream>
- #include<unistd.h>
- #include<pthread.h>
- #include <cstring>
- using namespace std;
- void* routine(void* arg){
- string name = static_cast<const char*>(arg);
- int cnt = 5;
- while(cnt--)
- {
- cout<<"我是子线程,名字:"<<name<<endl;
- sleep(1);
- }
- }
- int main(){
- pthread_t tid;
- pthread_create(&tid,nullptr,routine,(void*)"thread-1");
- //线程分离
- pthread_detach(tid);
- int cnt = 5;
- while(cnt--){
- cout<<"我是主线程"<<endl;
- sleep(1);
- }
- int n = pthread_join(tid,nullptr);
-
- if(n!=0){
- cout<<"pthread_join error"<<endl;
- }else{
- cout<<"pthread_join success"<<endl;
- }
- }
复制代码 一个被分离的历程,调用join会失败。
pthread_join()失败,就会返回0。
线程本身设置分离
- #include<iostream>
- #include<unistd.h>
- #include<pthread.h>
- #include <cstring>
- using namespace std;
- void* routine(void* arg){
- pthread_detach(pthread_self());
- string name = static_cast<const char*>(arg);
- int cnt = 5;
- while(cnt--)
- {
- cout<<"我是子线程,名字:"<<name<<endl;
- sleep(1);
- }
- return nullptr;
- }
- int main(){
- pthread_t tid;
- pthread_create(&tid,nullptr,routine,(void*)"thread-1");
-
- int cnt = 5;
- while(cnt--){
- cout<<"我是主线程"<<endl;
- sleep(1);
- }
- int n = pthread_join(tid,nullptr);
-
- if(n!=0){
- cout<<"pthread_join error"<<endl;
- }else{
- cout<<"pthread_join success"<<endl;
- }
- }
复制代码
创建多线程
- #include <iostream>
- #include <string>
- #include <vector>
- #include <unistd.h>
- #include <pthread.h>
- #include <cstdio>
- #include <cstring>
- 创建多线程
- const int num = 10;
- void *routine(void *args)
- {
- sleep(1);
- std::string name = static_cast<const char *>(args);
- delete (char*)args;
- int cnt = 5;
- while (cnt--)
- {
- std::cout << "new线程名字: " << name << std::endl;
- sleep(1);
- }
- return nullptr;
- }
- int main()
- {
- // char id[64];多执行流访问同一份资源,有危险。
- std::vector<pthread_t> tids;
- for (int i = 0; i < num; i++)
- {
- pthread_t tid;
- // bug??
- char *id = new char[64];//需要这么创建
- snprintf(id, 64, "thread-%d", i);
- int n = pthread_create(&tid, nullptr, routine, id);
- if (n == 0)
- tids.push_back(tid);
- else
- continue;
- }
- for (int i = 0; i < num; i++)
- {
- // 一个一个的等待
- int n = pthread_join(tids[i], nullptr);
- if (n == 0)
- {
- std::cout << "等待新线程成功" << std::endl;
- }
- }
- return 0;
- }
复制代码 监控 :while :; do ps -aL | head -1 && ps -aL | grep thread; sleep 1; done
注意点:
(1)
多执行流访问同一份资源,有危险。 - char id[64];多执行流访问同一份资源,有危险
复制代码 很大概率会引起,数据修改没导致结果不精确
(2) 为每一个线程本身创建一个,制止
多执行流访问同一份资源
线程ID及历程地址空间布局
- • pthread_create函数会产⽣⼀个线程ID,存放在第⼀个参数指向的地址中。
- 该线程ID和前⾯说的 线程ID不是⼀回事。
- • 前⾯讲的线程ID属于进程调度的范畴。因为线程是轻量级进程,
- 是操作系统调度器的最⼩单位, 所以需要⼀个数值来唯⼀表⽰该线程。
- • pthread_create函数第⼀个参数指向⼀个虚拟内存单元,
- 该内存单元的地址即为新创建线程的线 程ID,属于NPTL线程库的范畴。
- 线程库的后续操作,就是根据该线程ID来操作线程的。
- • 线程库NPTL提供了pthread_self函数,可以获得线程⾃⾝的ID:
复制代码
动态库的链接类比:pthread库的链接
过程:
磁盘内里的库文件,加载到物理内存内里,在把物理内存内里的库文件映射到mmap动态映射区域,大概可以叫做共享区。
如许历程本身的代码就可以访问到pthread库的内部函数大概数据
在库中对线程的管理:
线程的概念是在库中维护的,在库内,就肯定会存在多个被创建好的线程,库要不要管理线程呢?要!
先形貌,在构造!!
创建线程形貌符
mmap内里对线程的管理- mmap:动态映射区域
- TCB:线程的属性
- 线程局部存储:暂时未知
- 线程栈:暂时未知
- 创建的返回值:
- pthread_t tid:线程在库中,对应的管理快的虚拟地址。
- 线程函数运行完,返回值,管理快还存在,
- 所以线程需要join,
- join通过tid拿到线程管理快返回信息后,把管理快释放掉
复制代码 (1)在一个线程中,运行完一个函数,那么,线程就会返回一个void*的值,*ret,但是管理线程的管理快并不会由于函数的实行完而消散,这里就会造成内存走漏,以是我们必要pthread_join,来获取返回值,而且释放管理块的空间
(2)pthread_join工作流程:利用join,就可以得到该线程的返回值,*ret,然后释放管理块的空间,join的参数,tid拿到管理快的起始假造地址,就可以访问该管理快,&ret则用来吸收返回值
(3) join:为什么要用二级指针来担当线程的返回值???便于修改值。单传不可修改值。
一个线程是怎么被创建的???
线程的一部门概念一部门在库内里实现:
pthread_create(&tid)创建线程时,起首会在pthread库内里创建一个线程的布局体struct_pthread(线程管理块)然后把id,返回给传入的tid,作为线程在pthread库的假造地址,后序好对线程举行操纵。
一部门在PCB内里实现:
在内核中创建轻量级历程,通过clone来举行实现。(可以在看一遍,做更深了解)
例子:
我叫张三打饭
我是线程,维护信息(线程管理块)
张三给我打饭,根据信息给我打饭,张三是轻量级历程,去给我实行
总结:
ID:线程控制块的起始地址
返回值:线程实行完,的返回值,被join回收
分离:joinable = 0线程分离
joinable!=0,线程不分离
joinable线程标记位
|