
一. 线程函数
线程工作函数 也就是线程将来会调度的执行流和维护的栈空间函数
cpp
void* work(void*arg){
// 传过来的任意指针可以进行强转
// 这里用静态强转好了 ,加入传入的数据是int指针
int*count = static<int*>(arg);
// 可以设置正常结束线程的返回值 也可以设置nullptr
return nullptr
}
cpp
功能:创建一个新的线程
原型
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;失败返回错误码---这一点很重要
· 传统的一些函数是,成功返回0,失败返回-1,并且对全局变量errno赋值以指示错误。
pthreads函数出错时不会设置全局变量errno(而大部分其他POSIX函数会这样做)。而是将错误代码通 过返回值返回
pthreads同样也提供了线程内的errno变量,以支持其它使用errno的代码。对于pthreads函数的错误,
建议通过返回值业判定,因为读取返回值要比读取线程内的errno变量的开销更小
1.1 线程创建和使用demo
a. 创建一个线程 ,如果创建失败直接让进程退出 exit
b. 主线程和创建的线程 无限循环打印
cpp
void *work(void *arg)
{
for (;;)
{
std::cout << "im thread1" << std::endl;
sleep(1);
}
}
int main()
{
pthread_t tid;
int r = pthread_create(&tid, nullptr, work, nullptr);
if (r != 0)
{
exit(1);
}
for (;;)
{
std::cout << "im mainthread1" << std::endl;
sleep(1);
}
return 0;
}
1.2 进程的退出取决于?
进程的退出取决于
1.你主动调用exit了 ,或者return 最后被_Start获取到main函数的返回值
2. 异常退出,受到信号了
3. 没有执行流了
来思考一个问题并证明,我们都知道用phtread_exit() 可以让线程退出,假设我让主线程退出会让整个进程退出吗? 不会demo代码如下
cpp
void *work(void *arg)
{
for (;;)
{
std::cout << "im thread1" << std::endl;
sleep(1);
}
}
int main()
{
pthread_t tid;
int r = pthread_create(&tid, nullptr, work, nullptr);
if (r != 0)
{
exit(1);
}
for (;;)
{
std::cout << "im mainthread1" << std::endl;
sleep(1);
// pthread_t cur_tid = pthread_self();
pthread_exit(nullptr);
}
return 0;
}
output
结果是子线程一直打印 进程不退出
二 NPTL 的线程库图解 &&linux的轻量级进程
2.1 轻量级进程
- linux是没有单独的线程结构体的,windos操作系统倒是有tcb(thread)模块
2.简单来说作为一个执行流,线程跟进程真的很像,可是线程作为进程资源的一部分,而 且相关调度算法也跟进程非常的像,所以linux认为没必要那么复杂那么"重"- 在以 linux设计理念 " 一切皆文件,模块化设计,简洁高效,拥抱开源" ,所以复用咯 ,只有轻量级进程 LWP (light weight process) 正常情况下 主线程tid 和她的进程pid一样用
ps -L (查看)如下图
2.3 posix线程库
a. NPTL 是 Native POSIX Thread Library, (原生的posix标准的线程库),**linux只实现了基本的轻量级进程但是没有封装我们的线程呀,**意味着我们要自己去封装LCB(轻量级进程)的接口,以及管理我们真正意义的线程。
b. 如何组织一个线程? 以及如何管理: ---》 "先描述,在组织" 把线程描述成一个tcb结构体,然后在结构体内部没有用链表又或者将来用数组组织起来成为一个链表或者结构体。
我们来看看,posix的实现方案如下

cpp
// 线程的工作函数
void* work(void* arg){
int a =0;
satic sa =0;// 静态变量 将来会成为全局共享
//// io流底层封装的原生操作使用的是全局缓冲区不保证线程安全也是共享资源
printf("hello");
// 将来malloc获取的时候本质上也是全局变量管理的内存也是全局资源
malloc(sizeof(20));
}
线程有自己的: 1. 独立栈帧空间
独立信号屏蔽字
线程的局部存储 用__thread表示
相关更详细的线程安全以及posix独立性参考本人博客:
2.4 线程的退出&&取消
线程退出
cpp
pthread_exit函数
------需要注意,pthread_exit或者return返回的指针所指向的内存单元必须是全局的或者是用malloc分配的,不能在线程函 尽量用动态内存的指针哈
数的栈上分配,因为当其它线程得到这个返回指针时线程函数已经退出了。
pthread_cancel函数
功能:线程终止
原型
void pthread_exit(void *value_ptr);
参数
注意 phtread传参的是一个一级指针,指向之后线程退出后的返回值是 这个家伙
// 而我们的join是用二级指针,是为了获取这个一级指针
value_ptr:value_ptr不要指向一个局部变量。
返回值:无返回值,跟进程一样,线程结束的时候无法返回到它的调用者(自身)
取消指定线程
cpp
pthread_cancel函数
功能:取消一个执行中的线程
原型
int pthread_cancel(pthread_t thread);
1. 参数
传入指定的tid 然后取消指定的线程,
2. 返回值
线程库相关的函数错误码通常用返回值来表示,
,其次被调用cancle取消的线程返回值是一个宏值
(void*)-1 转指针后会是一个巨大的值
无符号的 之后再转整数可能会溢出哦(如果用int 存放)
thread:线程ID
3. 该函数的返回值
返回值:成功返回0;失败返回错误码
//线程cancel的本质是给线程设置标记表示cancel 只有再次
4. 原理
// 进入内核 线程库会检测她的canceltest()函数
// 所以如果线程工作函数一直没有相关的系统调用就不会cancel
由上文posix线程的本质我们可以理解,所有线程始终在同一个进程中,公用一个地址空间
线程之间可以通过全局/静态数组等方式获取,或者主线程定义的变量,也可以通过线程传参的方式被线程获取 那么也就有了其他线程获取其他线程的tid的能力
综合上面两点我们也就有实力在别的线程主动cancel指定的线程
获取到cancle的线程的返回值宏:

cpp
int main()
{
std::cout << (void *)-1 << std::endl;
return 0;
}
输出

2.5 pthread_join 线程等待
cpp
功能:等待线程结束
原型
// 跟许多执行流一样我们都想了解一个执行流的退出状态,如进程我们通过
// waitpid获取进程的退出状态信息 线程退出我们通过join函数来获取退出状态
// 通过参数 void** value_ptr
int pthread_join(pthread_t thread, void **value_ptr);
参数
thread:线程ID
value_ptr:它指向一个指针,后者指向线程的返回值
返回值:成功返回0;失败返回错
线程退出情况: 和返回值
线程正常退出,如线程函数内部调用 return void*
线程主动退出,如当触发 void pthread_exit(void *retval); 内核将线程退出,返回值是你指向的retval值,尽量用动态内存吧,
其实你可以理解1,2都是最后都是调用线程的pthread_exit();
- 线程异常退出,如调用int pthread_cancel(pthread_t thread); 这时候返回值是一个(void*)-1, 也就是我们说的之前那个宏
pthread_join(tid)的特点: a. 调用该函数的线程强制被阻塞 直到tid被等待成功
线程tid的本质和复用&&验证cancle的情况
cpp
// 线程1
void *work1(void *arg)
{
std::cout << "thread1 工作中" << std::endl;
sleep(1);
int *pret = new int(1);
return (void *)pret;
}
// 线程2工作函数
void *work2(void *arg)
{
std::cout << "thread2 工作中" << std::endl;
sleep(1);
int *pret = new int(2);
return (void *)pret;
}
// 线程3 被cancel掉
void *work3(void *arg)
{
std::cout << "thread3 工作中" << std::endl;
// sleep(10);
while (1)
{
// cancel 有一个要求就是进入内核中
// 才会被调用
sleep(2);
;
}
int *pret = new int(3);
// 假设正常返回的话 返回3
return (void *)pret;
}
int main()
{
pthread_t tid;
pthread_create(&tid, nullptr, work1, nullptr);
int *ret;
pthread_join(tid, (void **)&ret);
std::cout << "线程2的tid是:" << tid << "线程1退出返回值是" << *ret << std::endl;
// 线程2
ret = nullptr;
pthread_create(&tid, nullptr, work2, nullptr);
pthread_join(tid, (void **)&ret);
std::cout << "线程2的tid是:" << tid << " 线程2退出返回值是" << *ret << std::endl;
// 线程3
pthread_create(&tid, nullptr, work3, nullptr);
void *myret;
// sleep(2);
// 将来退出的返回值就是一个 (void*)-1 这个指针是一个很大的无符号的-1的整数地址
pthread_cancel(tid);
pthread_join(tid, (void **)&myret);
if (myret == PTHREAD_CANCELED)
{
printf("被cancel了 因为");
// C++的底层封装的是流对象的格式化输出流到缓冲区有不稳定的情况可能会丢包 我们用
// c语言的接口会更稳定
std::cout << "线程3的tid是:" << tid << " 线程3被cancel 退出返回值是" << myret << std::endl;
// std::cout << std::fflush << std::endl;
}
else
{
std::cout << "线程3的tid是:" << tid << " 线程3被正常退出返回值是" << myret << std::endl;
}
sleep(10);
return 0;
}

a. 三个线程,1线程和2线程都是实现都是一样的,只不过3线程 被cancel了
b. 第三个线程被cancel了 ,用printf输出了 但是cout一直不输出哈哈哈 原因我查了一些资料主要执行c++的用户层输出缓冲区不稳定
c. 能看见集合线程1和2是统一的tid ,说明tid一定会被复用,
三 . 线程分离&&joinable
3.1 pthread_detach 原理
1.默认情况下,新创建的线程是joinable的,线程退出后,需要对其进行pthread_join操作,否则无法释放资源,从而造成系统泄漏。
2. 如果不关心线程的返回值,join是一种负担,这个时候,我们可以告诉系统,当线程退出时,自动释放线程资源
3. 这个机制很像进程退出的时候父进程直接设置signal(SIG_CHLD,SIG_IGN);
cpp
#include <pthread.h>
int pthread_detach(pthread_t thread);
原理:
该方法让对应的线程标记为detached状态
区别于joinable状态,一旦设置以为者指定线程自己回收
3.2 detached状态与joinable是不可同时存在的
当我们的线程处于detached状态意味着不能像正常的线程那样被join回收了,如下代码可以被验证如下图就可以立即啦,如果你拿一个已经detach的线程去进行一个join,直接给你返回一个错误码22
b. 标记为 :// 一定要深刻立即 线程的状态都是标记位要让他设置完之后
// 然后让他触发回到内核态调整之后在处理回收啊 exit等其他操作


cpp
// 线程detach
void *work(void *arg)
{
// 内部进行detach
pthread_detach(pthread_self());
printf("%s", (const char *)arg);
return nullptr;
}
int main()
{
pthread_t tid;
pthread_create(&tid, nullptr, work, (void *)"线程1 run");
sleep(1); // 这里很重要
// 一定要深刻立即 线程的状态都是标记位要让他设置完之后
// 然后让他触发回到内核态调整之后在处理回收啊 exit等其他操作
int n;
if ((n = pthread_join(tid, NULL)) == 0)
{
// 等待成功返回0
std::cout << "成功等待" << std::endl;
}
else
{
std::cout << "错误 等待失败 错误码 errno " << n << std::endl;
}
return 0;
}

