1. Linux线程控制
1.1 创建线程
1. pthread_create
#include<pthread.h>
功能:创建⼀个新的线程
原型:
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *
(*start_routine)(void*), void *arg);
参数:
thread:输出型参数,返回线程ID
attr:设置线程的属性,attr为NULL表⽰使⽤默认属性(我们这里暂时先填NULL,不用管)
start_routine:是个函数地址,线程启动后要执⾏的函数
arg:传给线程启动函数的参数
返回值:成功返回0;失败返回错误码
解释一下 arg:这个就相当于 新线程流执行 start_routine里面传的参数 即start_routine(arg)!
如果不需要传递参数,可以设为 NULL;如果需要传递多个参数,通常的做法是将它们打包成一个结构体,然后将该结构体的指针作为 arg 传入。
而pthread_t本质上是一个无符号长整型!
案例代码:
#include<iostream>
#include<cstdio>
#include<string>
#include<pthread.h>
#include<unistd.h>
void *Routine(void* args)
{
std::string name = static_cast<const char*>(args);
while(true)
{
std::cout<<"我是新线程:"<<name<<std::endl;
sleep(1);
}
}
int main()
{
pthread_t tid;
int n = pthread_create(&tid,nullptr,Routine,(void*)"thread-1");
(void)n;
printf("new thread id: 0x%lx\n",tid);
while (true)
{
std::cout<<"我是主线程..."<<std::endl;
sleep(1);
}
return 0;
}
结果:
这个地址究竟是什么,后面会讲!
2. pthread_self
pthread_self 是 POSIX 线程库中的一个函数, 用于获取当前调用线程自身的线程标识符(thread identifier) ,其返回类型为 pthread_t。该函数无需传入任何参数,调用后直接返回当前线程的唯一 ID,常用于线程内部识别自身身份、日志记录或调试。
pthread_t tid = pthread_self();
printf("Current thread ID: %lu\n", (unsigned long)tid);
需要注意的是,pthread_self() 返回的是用户空间的线程 ID,与操作系统内核中的轻量级进程 ID(LWP ID)不同!!
将上面代码的新线程执行函数 改一下
void *Routine(void* args)
{
std::string name = static_cast<const char*>(args);
while(true)
{
// std::cout<<"我是新线程:"<<name<<std::endl;
printf("我是新线程:%s,tid:0x%lx\n",name.c_str(),pthread_self());
sleep(1);
}
}
效果:
3. 如何创建多线程
#include<iostream>
#include<cstdio>
#include<string>
#include<pthread.h>
#include<unistd.h>
//因为线程资源共享 -- 所以代码共享!
void PrintName(const std::string &name)
{
printf("我是新线程:%s,tid:0x%lx\n",name.c_str(),pthread_self());
}
//Routine:多个进程流同时进入Routine:函数被重入了!!
void *Routine(void* args)
{
std::string name = static_cast<const char*>(args);
printf("------------------\n");
while(true)
{
// std::cout<<"我是新线程:"<<name<<std::endl;
PrintName(name);
sleep(1);
}
}
int main()
{
//创建多线程
const int num = 10;
for(int i = 0; i< num;i++)
{
pthread_t tid;
//创建线程名
char threadname[64];
snprintf(threadname,sizeof(threadname),"thread-%d",i+1);
int n = pthread_create(&tid,nullptr,Routine,threadname);
(void)n;
}
while (true)
{
//std::cout<<"我是主线程..."<<std::endl;
printf("我是主线程tid:0x%lx,pid:%d\n",pthread_self(),getpid());
sleep(1);
}
}
效果:
来个结论: 多线程资源共享 无论是堆 还是栈,虽说每个线程都有自己独立的栈 但是你想还是能很简单拿到其虚拟地址然后在另一个线程内进行修改的!
4. 线程异常

当创建到线程8 此时会出现 除零错误

此时会导致所有线程崩溃!!
在多线程中,任意一个线程奔溃,会导致整个进程挂掉!从而会导致所有线程跟着一起挂!!即 健壮性也叫鲁棒性特别差!!
5. 初谈数据不一致问题
我们发现 打印的时候 随机性特别强


又是少1 或者少9 少10 这是为什么?
在 main 函数的循环中:
for(int i = 0; i< num;i++)
{
// ...
char threadname[64]; // <--- 这是一个栈上的局部数组
snprintf(threadname,sizeof(threadname),"thread-%d",i+1);
// 将局部变量的地址传递给了新线程
int n = pthread_create(&tid,nullptr,Routine,threadname);
} // <--- 循环结束,threadname 被销毁,内存失效!
threadname 是在 for 循环内部定义的局部数组。当一次循环结束时,该数组占用的内存会被释放或覆盖,可是你的for循环是一直进行的,而你的threadname值可能还来得及没被新线程创建在自己的栈里就已经销毁了(因为多线程的调度顺序我们不清楚)!
而我们每次都是拿到 threadname 这个临时缓冲区的地址!是 创建的10个 新线程的共享资源!销毁后恢复 销毁后恢复 不断重复(栈空间的复用机制) 每一的新线程的 void* args都指向了 threadname;同时主线程也再不断的对其进行修改!就会导致比如 新线程还没拷贝,而threadname已经被主线程又修改了,然后新的threadname被拷贝到了新线程!
那么该如何解决?
为每个线程 new 一块内存,用完在线程内 delete!
for(int i = 0; i< num;i++)
{
pthread_t tid;
//创建线程名
char* threadname = new char[64];
snprintf(threadname,64,"thread-%d",i+1);
int n = pthread_create(&tid,nullptr,Routine,threadname);
(void)n;
}

此时我们发现 thread - 1、2、3、4、5、6、7、8、9、10 都有了!!
6. 关于传参问题
在 线程创建的void* args参数中 void* 表示任意参数类型 意味着C++可以传类直接调用函数 而C语言可以传结构体以回调函数的形式调用函数。
1.2 线程终⽌
如果需要只终⽌某个线程⽽不终⽌整个进程,可以有三种⽅法:
- 从线程函数
return。这种方法对主线程不适用,从main函数return相当于调用exit。 - 线程可以调用
pthread_exit终止自己。 - 一个线程可以调用
pthread_cancel终止同一进程中的另一个线程。
1. pthread_exit函数
功能:线程终⽌
原型:
void pthread_exit(void *value_ptr);
参数:
value_ptr:value_ptr不要指向⼀个局部变量,不想用可以传nullptr
这个参数具体的应用 参考1.3中的 线程退出信息综述
返回值:
⽆返回值,跟进程⼀样,线程结束的时候⽆法返回到它的调⽤者(⾃⾝)
主线程可以结束自己 不过会处于defunct(已失效的)状态 即 已终止但未被回收!

2.pthread_cancel函数
功能:取消⼀个执⾏中的线程
原型:
int pthread_cancel(pthread_t thread);
参数:
thread:线程ID
返回值:成功返回0;失败返回错误码(和正常返回值一样 也是用int 类型接收))
三种场景的具体分析(我实验过了 感兴趣可以自己试试 不同版本的Linux系统可能会有些细微差别,我的是ubuntu22.04):
- 新线程或者主线程取消其他新线程: 这是最常见且安全的用法。
- 新线程取消自己: 这在语法上完全合法,但通常没有必要,因为线程可以直接调用
pthread_exit()来优雅地结束自己。 - 新线程取消主线程: 新线程无法取消主线程 ,有的机器会无事发生(比如我的),有的怎么执行直接卡住 但线程都在也没死亡
- 主线程取消自己: 主线程可以取消自己 不过会处于
defunct(已失效的)状态即 已终止但未被回收!
1.3 线程等待
新线程必须被主线程等待,不如会导致类似子进程那里的僵尸问题(必须)!主线程要获取新线程的执行结果(非必须)
ps:该线程PCB在内核被回收 但是我们无法像进程那样观测到该状态, 因为我们的ps -aL查看的是系统级的轻量级进程,在内核中确实已经将该线程释放 甚至对应PCB都没了 不过线程相关的管理信息在库中却依然存在!!
1. pthread_join
功能:等待线程结束 只要线程在joinable(可连接状态) 任何线程都可调用,只是我们常用主线程!
原型
int pthread_join(pthread_t thread, void **value_ptr);
参数:
thread:线程ID
value_ptr:它指向⼀个指针,是个输出型参数,后者指向线程的返回值
返回值:成功返回0;失败返回错误码(和正常返回值一样 也是用int 类型接收)!
这里的 void **value_ptr 获得的是 void* routine(void*) 的void*返回值!
就好比你想在参数那里 获得一个int的值 必须 通过int*;同理 你想获得void*的值 你必须通过void **
⚠️:void 表示空类型 在定义void a;的变量时候 几乎所有编译器都编译不过去 因为不同编译器的 void大小是不同的 有的是1或者0byte;但是 void*不一样 以32位系统为例子 地址总线宽度为32位,因此所有类型的指针大小均为 4字节!指针变量本质上是一个用来存储“内存地址”的变量;只是void*不强转无法被解引用罢了。
所以 void*里面 存储的是 32位的虚拟地址罢了!如 void* ret;如果你想获得 ret的地址 需要void** 而void** 里面填的就是 &ret!!
#include <iostream>
#include <cstdio>
#include <string>
#include <unistd.h>
#include "Task.hpp"
#include <cstdlib>
#include <ctime>
#include <vector>
#include <sys/syscall.h>
void * Routine(void* args)
{
std::string name =static_cast<const char*>(args);
while(true)
{
std::cout<<"new thread"<<std::endl;
sleep(5);
break;
}
return (void*)10;
}
int main()
{
pthread_t tid;
pthread_create(&tid,nullptr,Routine,(void*)"thread-1");
//1. 一般而言必须等待新线程退出,如果不等待,会导致类似僵尸进程的问题
//2. 为了获取新线程的执行结果
sleep(10);
void *retval = nullptr;
int n = pthread_join(tid,&retval);
if(n == 0)
{
std::cout<<"join success:" << (long long)retval<<std::endl;
//我这个是64位机器 所以 void* 是64字节 所以强转用long long
}
return 0;
}
这段代码的功能是: 在主线程中创建一个名为 “thread-1” 的新线程,该新线程会打印一条日志并休眠 5 秒后返回整数值 10;主线程在休眠 10 秒后调用 pthread_join 等待新线程完全退出并回收其资源,最终将新线程的返回值(10)打印到控制台。
2. 线程退出信息综述
问题: 为什么pthread_join获得的退出信息,没有进行异常分析?
因为 新线程出异常,进程会全部退出!根本就没机会join成功,不需要关心异常!
其实 线程退出信息,并没有规定一定要是整数!可以返回 字符串,也能返回类对象(不过注意 类得是 new出来的)!!
整数(例如 return (void*)10;)属于按值传递。当你返回一个整数时,系统仅仅是将“10”这个数值本身拷贝到了 void* 指针的 8 个字节(64位系统)中。它不依赖任何外部内存地址,因此不需要动态开辟堆空间,也不会面临内存销毁的问题
| 数据类型 | 返回局部变量/数组地址 | 返回字符串字面量 | 返回 new/malloc 的指针 |
|---|---|---|---|
| 数组 / C字符串 | ❌ 危险(栈销毁) | ✅ 安全(常量区) | ✅ 安全(需手动释放) |
| std::string / vector / 类 | ❌ 危险(栈销毁) | 不适用 | ✅ 安全(需手动释放,STL自动申请自动释放) |
| 纯整数 / 纯指针 | ✅ 安全(按值拷贝) | 不适用 | 不适用 |
这是因为 因为void* 里面存的是数字的值 而 对于类这种存的是它的地址!
例子:
#include <iostream>
#include <cstdio>
#include <string>
#include <unistd.h>
#include "Task.hpp"
#include <cstdlib>
#include <ctime>
#include <vector>
#include <sys/syscall.h>
class Res
{
public:
int code;
std::string name;
std::string info;
};
void * Routine(void* args)
{
std::string name =static_cast<const char*>(args);
while(true)
{
std::cout<<"new thread"<<std::endl;
sleep(3);
break;
}
Res* res = new Res();
res->code = 10;
res->name=name;
res->info="我这个进程已经完蛋了,你们继续努力";
return (void*)res;
}
int main()
{
pthread_t tid;
pthread_create(&tid,nullptr,Routine,(void*)"thread-1");
//1. 一般而言必须等待新线程退出,如果不等待,会导致类似僵尸进程的问题
//2. 为了获取新线程的执行结果
sleep(5);
void * retval = nullptr;
int n = pthread_join(tid,(void**)&retval);
if(n == 0)
{
std::cout<<"join success:" << ((*(Res*)retval)).code<<std::endl;
std::cout<<"join success:" << ((*(Res*)retval)).name<<std::endl;
std::cout<<"join success:" << ((*(Res*)retval)).info<<std::endl;
//我这个是64位机器 所以 void* 是64字节 所以强转用long long
}
return 0;
}
这段代码演示了如何在多线程编程中安全地传递复杂对象:
主线程创建一个新线程并传入字符串参数,新线程休眠3秒后在堆上动态分配一个包含状态码、名称和信息的Res类对象,将其地址作为返回值返回;主线程休眠5秒后调用pthread_join等待新线程结束并获取该返回值,随后通过强制类型转换解引用该指针,成功打印出子线程返回的Res对象中的各项数据。
调⽤该函数的线程将挂起等待,直到id为thread的线程终⽌。thread线程以不同的⽅法终⽌,通过pthread_join得到的终⽌状态是不同的,总结如下:
- 如果thread线程通过return返回 ,
value_ptr所指的单元里存放的是thread线程函数的返回值。 - 如果thread线程被别的线程调用
pthread_cancel终止 ,value_ptr所指的单元里存放的是常数PTHREAD_CANCELED(这个是个整数!)。

- 如果thread线程是自己调用
pthread_exit终止的 ,value_ptr所指的单元存放的是传给pthread_exit的参数。 - 如果对thread线程的终止状态不感兴趣 ,可以传
NULL给value_ptr参数。
1.4 线程的程序替换
问题: 线程能否调用execl这样的函数进行程序替换?
答: 代码上是能的,但是线程的程序替换相当于进程的程序替换!会将当前进程的代码数据和页表都修改,会导致其他线程全部出错!
所以 怎么让线程进行程序替换呢?? 在线程内部调用fork,然后在新进程内调用execl!
1.4 线程分离
- 默认情况下,新创建的线程是
joinable(可连接的)的,这个属性 说人话就是: 线程退出后,必须需要对其进行pthread_join操作,否则无法释放资源,从而造成系统泄漏。 - 如果不关心线程的返回值,join是一种负担,这个时候,我们可以告诉系统,当线程退出时,自动释放线程资源(这种状态 就是
分离状态)。
此时就可以把目标被等待的线程设置为:分离状态!
1. pthread_detach
pthread_detach 是 POSIX 线程库中的一个函数,用于将指定线程设置为“分离状态”(detached state)。处于该状态的线程在终止时,系统会自动回收其所有资源,无需其他线程调用 pthread_join 来显式等待和清理。
int pthread_detach(pthread_t thread);
成功返回 0;失败返回错误码(和正常返回值一样 也是用int 类型接收)!
如:
- ESRCH:指定的线程不存在或已被回收。
- EINVAL:线程已处于分离状态。
- 本质是个宏定义的常数 可以自己去查
可以是线程组内其他线程对⽬标线程进⾏分离,也可以是线程⾃⼰分离:
pthread_detach(pthread_self());
当一个线程处于 分离状态,死亡后 被 pthread_join必然失败!
joinable和分离是冲突的,⼀个线程不能既是joinable⼜是分离的。









线程控制&spm=1001.2101.3001.5002&articleId=162109526&d=1&t=3&u=cdba0536e7b74273bf7a861aa6a7e4ad)
1306

被折叠的 条评论
为什么被折叠?



