代码1:验证join可以去的线程执行完后的退出码/返回值
cpp
#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证明::给线程传递的参数和返回值,可以是任意类型 ,下面的例子是类类型
cpp
#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;
}

线程终止
如果需要只终⽌某个线程⽽不终⽌整个进程,可以有三种⽅法:
从线程函数return。这种⽅法对主线程不适⽤,从main函数return相当于调⽤exit。
线程可以调⽤pthread_exit终⽌⾃⼰。
⼀个线程可以调⽤pthread_cancel终⽌同⼀进程中的另⼀个线程。
pthread_exit:线程终止
cpp
功能:线程终⽌
原型:
void pthread_exit(void *value_ptr);
参数:
value_ptr:value_ptr不要指向⼀个局部变量,就是指的一个变量
pthread_exit 的参数 value_ptr 是一个指向某个值的指针,该值将作为线程的返回值。这个返回值可以被主线程通过 pthread_join 获取。
返回值:
⽆返回值,跟进程⼀样,线程结束的时候⽆法返回到它的调⽤者(⾃⾝)
和return是等价的。
线程等待 :pthread_join
为什么需要线程等待?
• 已经退出的线程,其空间没有被释放,仍然在进程的地址空间内。用来释放空间
• 创建新的线程不会复⽤刚才退出线程的地址空间。
cpp
功能:等待线程结束
原型
int pthread_join(pthread_t thread, void **value_ptr);
参数:
thread:线程ID
value_ptr:它指向⼀个指针,后者指向线程的返回值
返回值:成功返回0;失败返回错误码
进程以不同的方式终止,pthread_join得到的线程返回值是不一样的
调⽤该函数的线程将挂起等待,直到id为thread的线程终⽌。thread线程以不同的⽅法终⽌,通过 pthread_join得到的终⽌状态是不同的,总结如下:
cpp
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是⼀种负担,这个时候,我们可以告诉系统,当线程退出时,⾃ 动释放线程资源。
如果主线程不想再关心新线程,而是当新线程结束的时候,让他自己释放??
cpp
设置新线程为分离状态
技术层面: 线程默认是需要被等待的,joinable。如果不想让主线程等待新线程
想让新线程结束之后,自己退出,设置为分离状态(!joinable or detach) // todo
理解层面:线程分离,主分离新,新把自己分离。
分离的线程,依旧在进程的地址空间中,进程的所有资源,被分离的线程,依旧可以访问,可以操作。
主不等待新线程。
分离操作
如果线程被设置为分离状态,不需要进行join,join会失败!!
主线程设置分离
cpp#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。
线程自己设置分离
cpp#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; } }

创建多线程
cpp
#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)多执行流访问同一份资源,有危险。
cpp
char id[64];多执行流访问同一份资源,有危险
很大概率会引起,数据修改没导致结果不准确
(2) 为每一个线程自己创建一个,避免多执行流访问同一份资源
cpp
多执行流访问同一份资源
线程ID及进程地址空间布局
cpp
• pthread_create函数会产⽣⼀个线程ID,存放在第⼀个参数指向的地址中。
该线程ID和前⾯说的 线程ID不是⼀回事。
• 前⾯讲的线程ID属于进程调度的范畴。因为线程是轻量级进程,
是操作系统调度器的最⼩单位, 所以需要⼀个数值来唯⼀表⽰该线程。
• pthread_create函数第⼀个参数指向⼀个虚拟内存单元,
该内存单元的地址即为新创建线程的线 程ID,属于NPTL线程库的范畴。
线程库的后续操作,就是根据该线程ID来操作线程的。
• 线程库NPTL提供了pthread_self函数,可以获得线程⾃⾝的ID:
动态库的链接类比:pthread库的链接
过程:
磁盘里面的库文件,加载到物理内存里面,在把物理内存里面的库文件映射到mmap动态映射区域,或者可以叫做共享区。
这样进程自己的代码就可以访问到pthread库的内部函数或者数据
在库中对线程的管理:
线程的概念是在库中维护的,在库内,就一定会存在多个被创建好的线程,库要不要管理线程呢?要!
先描述,在组织!!
创建线程描述符
mmap里面对线程的管理
cpp
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线程标记位