📚 博主的专栏
关联文章:【Linux】线程的概念、虚拟地址最终理解(这一篇足够)-优快云博客
pthread_create()-----线程的创建
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine)(void *), void *arg);
参数说明:
thread
:存储新线程ID的指针,是一个输出型参数
attr
:线程属性(NULL 表示默认),线程的调度优先级。
start_routine
:线程入口函数,返回值类型void*,参数类型也是void*
arg
:传递给入口函数的参数返回值:
成功:返回
0
。失败:返回错误码(如
EINVAL
表示无效参数)。示例:
#include <pthread.h> #include <stdio.h> void* thread_function(void* arg) { printf("Thread is running\n"); return NULL; } int main() { pthread_t tid; int ret = pthread_create(&tid, NULL, thread_function, NULL); if (ret != 0) { perror("pthread_create failed"); return -1; } return 0; }
pthread_join()-----
等待线程结束
int pthread_join(pthread_t thread, void **retval);
参数:
pthread_t thread
:要等待的线程ID。
void **retval
:存储线程返回值的指针(可选,传NULL
表示不关心返回值)也就是新线程结束时,线程实现的函数的类型为void*的返回值,然后被主线程获取。返回值:
成功:返回
0
。- 失败:返回错误码(如
ESRCH
表示线程不存在)。示例:
pthread_join(tid, NULL); // 等待线程结束
组合示例:这段代码通过 pthread_create
创建新线程执行循环打印任务,主线程使用 pthread_join
等待新线程结束以保证进程正常退出,避免新线程未执行完就被强制终止。
问题1:main 和 new线程谁先运行? 不确定(同进程)
问题2:我们期望谁最后退出: main thread,如何保证呢?join来保证,不join,是否会阻塞,不会,当主线程退了,进程退出,因此新线程一并退出不join,会造成类似类似僵尸的问题
#include<iostream>
#include<string>
#include<pthread.h>
#include<unistd.h>
void *threadRun(void *args)
{
int cnt = 10;
while(cnt)
{
std::cout << "new thread run ... cnt " << cnt-- << std::endl;
sleep(1);
}
return nullptr;
}
int main()
{
pthread_t tid; //unsigned long int
//问题一:main 和 new线程谁先运行? 不确定(同进程)
int n = pthread_create(&tid, nullptr, threadRun, (void*)"thread 1");
if(n != 0)
{
std::cerr << "create thread error" << std::endl;
return 1;
}
std::cout << "main thread begin ..." << std::endl;
//问题二:我们期望谁最后退出: main thread,如何保证呢
n = pthread_join(tid, nullptr);//join来保证,不join,是否会阻塞,不会,当主线程退了,进程退出,因此新线程一并退出
if(n == 0) //不join,会造成类似类似僵尸的问题
{
std::cerr << "main thread wait success" << std::endl;
}
return 0;
}
运行:
while :; do ps -aL ; sleep 1; done
问题3:tid是什么样子?
直接进行打印时,可看到:tid的值很大,因此我们使用16进制来看
std::string PrintToHex(pthread_t &tid)
{
char buffer[64];
snprintf(buffer, sizeof(buffer), "0x%lx", tid);
return buffer;
}
// 问题3: tid 是什么样子的?是什么呢?
std::string tid_str = PrintToHex(tid); // 我们按照16进行打印出来
std::cout << "tid : " << tid_str << std::endl;
线程ID,TID是什么?是虚拟地址
问题4:全面看待给pthread_create:void *arg传参,我们可以传递任意参数类型,那么也可以传递类对象的地址
修改代码,查看所传的参数:
传变量值给新线程:
int a = 100;
int n = pthread_create(&tid, nullptr, threadRun, (void*)&a);
int a = *(int*)args;
int cnt = 10;
while(cnt)
{
std::cout << a << " run ... cnt " << cnt-- << std::endl;
sleep(1);
}
首先了解一个强转类型:static_cast ---> 安全级别的强转,是为了告诉编译器,从而对目标类型做安全性检查。这里就是(ThreadData*)
封装一个类:
// 可以给线程传递多个参数,甚至方法了
class ThreadData
{
public:
std::string name;
int num;
};
在threadRun(void *args):
void *threadRun(void *args)
{
//std::string name = (const char*)args;
//int a = *(int*)args; // warning
ThreadData *td = static_cast<ThreadData*>(args); // (ThreadData*)args
int cnt = 10;
while(cnt)
{
std::cout << td->name << " run ..." << ", cnt: " << cnt-- << std::endl;
break;
}
return nullptr;
}
在main函数当中:
pthread_t tid; // unsigned long int
// 问题一:main 和 new线程谁先运行? 不确定(同进程)
ThreadData td;
td.name = "thread-1";
td.num = 1;
int a = 100;
int n = pthread_create(&tid, nullptr, threadRun, (void *)&td);
运行结果:
由此可以知道主线程可以给线程传递多个参数,甚至方法。
注意:
1.ThreadData对象td,属于main函数栈上开辟的空间,新线程访问的是主线程栈上的变量
上面的方法不推荐,破坏了主线程的完整性和独立性,如果新线程有多个,这些新的线程就会访问了同一个,主函数栈上定义的临时变量,如果有线程修改了这个值,就会影响另一个线程。线程1就会认为自己也是线程2.
2.正确的做法是,建议在堆上申请一块空间,然后在堆上将这个临时变量拷贝给新线程
需要创建多个新线程的时候,就创建多个对象,再拷贝给新线程
然后注意删除掉:
void *threadRun(void *args)
{
//std::string name = (const char*)args;
//int a = *(int*)args; // warning
ThreadData *td = static_cast<ThreadData*>(args); // (ThreadData*)args
int cnt = 10;
while(cnt)
{
sleep(1);
std::cout << td->name << " run ... num is " << td->num << ", cnt: " << cnt-- << std::endl;
}
delete td;
return nullptr;
}
void **retval
:返回的是新线程退出时,函数的返回值void*,返回的是一个指针变量
retval
是一个输出型参数,是指针变量,是变量,在Linux中占8个字节,因此可以接受别人的返回值,指针就是一个数字, void *code = nullptr; 一定要注意,虽然是空指针,但是是开辟了空间的,因为Linux系统当中指针占8个字节,因此不能强转为int(int占4个字节),使用u_int64_t。
void *code = nullptr; //一定要注意,虽然是空指针,但是是开辟了空间的
n = pthread_join(tid, &code);
if (n == 0)
{
std::cerr << "main thread wait success, new thread exit code: " << (u_int64_t)code << std::endl;
}
一旦线程退出,函数的返回值就会放到code当中。
如图:主线程就能根据退出信息来判断新线程对任务的处理情况
问题5:全面看待线程函数返回:
运行结果:新线程崩掉,主线程也一起崩掉,程序出错:
这是因为,一旦野指针,出错的时候直接将进程干掉,因此所有线程都没了,
因此线程的返回,只考虑正确的返回,不考虑异常,因为一旦异常,整个进程就崩溃,包括主线程
不用担心因为主线程也崩溃而不能知道新线程的执行情况,因为,线程崩溃就相当于进程崩溃,退出信息将有进程的父进程收到。
也可以返回执行任务的的结构体:指派一个任务给线程,让线程执行任务
这段代码通过传递自定义类 ThreadData
到新线程中进行计算,主线程使用 pthread_join
等待子线程返回 ThreadResult
结果并打印
#include <iostream>
#include <string>
#include <pthread.h>
#include <unistd.h>
// 可以给线程传递多个参数,甚至方法了
class ThreadData
{
public:
int Excute()
{
return x + y;
}
public:
std::string name;
int x;
int y;
// other
};
class ThreadResult
{
public:
std::string print()
{
return std::to_string(x) + "+" + std::to_string(y) + "=" + std::to_string(result);
}
public:
int x;
int y;
int result;
};
void *threadRun(void *args)
{
ThreadData *td = static_cast<ThreadData *>(args); // (ThreadData*)args
ThreadResult *result = new ThreadResult;
int cnt = 10;
while (cnt)
{
sleep(1);
result->result = td->Excute();
result->x = td->x;
result->y = td->y;
break;
std::cout << td->name << " run ... " << ", cnt: " << cnt-- << std::endl;
// std::cout << td->name << " run ... num is " << td->num << ", cnt: " << cnt-- << std::endl;
}
delete td;
return (void *)result;
}
std::string PrintToHex(pthread_t &tid)
{
char buffer[64];
snprintf(buffer, sizeof(buffer), "0x%lx", tid);
return buffer;
}
int main()
{
pthread_t tid; // unsigned long int
// 问题一:main 和 new线程谁先运行? 不确定(同进程)
ThreadData *td = new ThreadData();
td->name = "thread-1";
td->x = 10;
td->y = 20;
int a = 100;
int n = pthread_create(&tid, nullptr, threadRun, td);
// int n = pthread_create(&tid, nullptr, threadRun, (void*)&td);
if (n != 0)
{
std::cerr << "create thread error" << std::endl;
return 1;
}
std::string tid_str = PrintToHex(tid); // 我们按照16进行打印出来
std::cout << "tid : " << tid_str << std::endl;
std::cout << "main thread begin ..." << std::endl;
void *code = nullptr;
n = pthread_join(tid, &code);
if (n == 0)
{
std::cerr << "main thread wait success, new thread exit code: " << (u_int64_t)code << std::endl;
}
return 0;
}
运行结果:
问题6:如何创建多线程-线程的批量化创建和等待
#include <iostream>
#include <string>
#include <pthread.h>
#include <unistd.h>
#include<vector>
const int num = 10;
void *threadRun(void *args)
{
std::string name = static_cast<const char*>(args);
while(true)
{
std::cout << name << " is running" << std::endl;
sleep(1);
}
}
int main()
{
std::vector<pthread_t> tids;
//问题6:如何创建多线程
for(int i = 0; i < num; i++)
{
// 1.有线程的id
pthread_t tid;
// 2.有线程的名字
char name[128];
snprintf(name, sizeof(name), "thread-%d", i+1);
pthread_create(&tid, nullptr, threadRun, name);
}
sleep(100);
}
虽然是有11个线程,但是出来的线程名很乱。我们想要看到的是有序地。虽然是按顺序创建,因为线程被创建,谁被调度不确定,公共区域被传给了所有的线程,连线程名都在不断的被覆盖。因此创建的时候要使用堆,然后再拷贝给新线程。
修改:
运行正确:
对所有的线程进行等待:
const int num = 10;
std::string PrintToHex(pthread_t &tid)
{
char buffer[64];
snprintf(buffer, sizeof(buffer), "0x%lx", tid);
return buffer;
}
void *threadRun(void *args)
{
std::string name = static_cast<const char*>(args);
while(true)
{
std::cout << name << " is running" << std::endl;
sleep(1);
break;
}
return args;
}
int main()
{
std::vector<pthread_t> tids;
//问题6:如何创建多线程
for(int i = 0; i < num; i++)
{
// 1.有线程的id
pthread_t tid;
// 2.有线程的名字
char *name = new char[128];
snprintf(name, 128, "thread-%d", i+1);
pthread_create(&tid, nullptr, threadRun, name);
// 3.保存所有新线程的 id
tids.emplace_back(tid);
}
// join todo
for(auto tid : tids)
{
void *name = nullptr;
pthread_join(tid, &name);
// std::cout << PrintToHex(tid) << " quit... "<< std::endl;
std::cout << (const char*)name << " quit... "<< std::endl;
delete (const char*)name;
}
}
运行结果:
问题7 - 线程的终止
1.新线程如何终止:函数return
2.主线程如何终止:main函数结束,主线程结束
可以使用exit()来结束新线程吗?不可以,exit()是专门用来结束进程的,如果在子线程运行完直接使用exit(),进程就直接退出了,那么主线程肯定也跟着一起退出了。
pthread_exit
--显式地终止调用线程
#include <pthread.h>
void pthread_exit(void *retval);
参数
void *retval
:指向线程返回值的指针。这个返回值可以通过pthread_join
函数获取。如果retval
是NULL
,则表示线程没有返回值。返回值
pthread_exit
函数不返回任何值。调用pthread_exit
后,线程会立即终止,后续操作将不再执行。控制权返回给线程库。用法
显式终止线程:
pthread_exit
用于显式地终止调用线程。与exit
函数不同,pthread_exit
仅影响调用它的线程,而不是整个进程。返回值:通过
pthread_exit
的retval
参数,线程可以返回一个指向返回值的指针。调用线程的其他线程可以通过pthread_join
函数获取这个返回值。资源释放:调用
pthread_exit
后,线程相关的资源(如线程栈和线程控制块)会被释放。如果线程在创建时分配了特定的资源(如动态分配的内存),需要在pthread_exit
之前手动释放这些资源。
新线程退出成功,主线程等待成功。pthread_cancel类似作用是取消一个线程,会发送一个取消请求给新线程。
pthread_cancel---
取消的目标线程的线程ID
#include <pthread.h>
int pthread_cancel(pthread_t thread);
参数
pthread_t thread
:指定要取消的目标线程的线程ID。返回值
成功:返回
0
。失败:返回非零错误码。例如:
ESRCH
:未找到目标线程。其他错误码可能因不同实现而异。
取消一个等待一个。如果一个线程是取消的,他的返回类型是一个有符号的整数。
最后只剩主线程,线程被取消,他的退出结果是: -1,这是在pthread库中被定义的宏。
问题8:在多线程的时候,是否能不join线程,让他执行完自己退出?--可以
pthread_detach()-----
将线程设置为分离状态,使其在后台独立运行
int pthread_detach(pthread_t thread);
参数:
pthread_t thread
:要分离的线程ID。返回值:
成功:返回
0
。失败:返回错误码。
示例:
pthread_detach(tid); // 分离线程
1.一个线程被创建,默认是joinable的:必须要被join的。
2.如果一个线程被分离,线程的工作状态分离状态,不需要/不能被join,依旧属于进程内部,但是不需要被等待join了。
pthread_self
函数
#include <pthread.h>
pthread_t pthread_self(void);
参数
无参数。
返回值
返回当前线程的线程ID,类型为
pthread_t
。在Linux系统中,pthread_t
是一个不透明的数据类型,通常是一个结构体,包含线程的内部信息。用法
获取当前线程ID:
pthread_self
用于获取当前正在执行的线程的ID。线程标识:线程ID在同一个进程中是唯一的,可以用于标识不同的线程。
线程管理:可以用于线程间的通信、同步和管理。
注意事项
线程ID的唯一性:线程ID在同一个进程中是唯一的,但在不同进程中可能相同。
线程ID的用途:线程ID可以用于线程间的通信,例如通过
pthread_join
、pthread_cancel
等函数。线程ID的类型:
pthread_t
是一个不透明的数据类型,具体实现可能因系统而异。
原来的等待后得到的返回值:
现在分离:
退出的返回值等于22,出错
修改代码:
运行结果:
这表示了,禁止等待,一等待就函数调用直接出错,就直接到主线程。
注意:就算新线程分离了,也仍然属于这个进程,如果发生了异常,还是会导致进程退出,主线程当然也退出。
主线程分离新线程:前提条件,新线程一定存在。主线程可以主动分离新线程
C++11已经实现多线程了:include<thread>
对比 pthread:无需手动管理 pthread_t
,避免 void*
类型转换风险
示例:
#include <iostream>
#include <string>
#include <pthread.h>
#include <unistd.h>
#include<vector>
#include<stdlib.h>
#include <thread>
//C++11已经支持多线程
void threadrun(std::string name, int num)
{
while(num)
{
std::cout << name << " num : " << num<< std::endl;
num--;
sleep(1);
}
}
int main()
{
std::string name = "thread-1";
std::thread mythread(threadrun, std::move(name), 10);
while(true)
{
std::cout << "main thread..." << std::endl;
sleep(1);
}
mythread.join();
return 0;
}
删除掉Makefile中的导入第三方库的选项:有可能无法编译通过,因此要支持多线程也要添加pthread库。
修改之后:需要引入 -lthread
C++11的多线程体系本质,就是对原生线程库的接口的封装
语言具有跨平台性
在语言层面上,就提供了同一种线程的创建方式,但是不同平台每一种库的实现就是不一样的
在学习文件操作的时候也是如此。
结语:
随着这篇博客接近尾声,我衷心希望我所分享的内容能为你带来一些启发和帮助。学习和理解的过程往往充满挑战,但正是这些挑战让我们不断成长和进步。我在准备这篇文章时,也深刻体会到了学习与分享的乐趣。
在此,我要特别感谢每一位阅读到这里的你。是你的关注和支持,给予了我持续写作和分享的动力。我深知,无论我在某个领域有多少见解,都离不开大家的鼓励与指正。因此,如果你在阅读过程中有任何疑问、建议或是发现了文章中的不足之处,都欢迎你慷慨赐教。
你的每一条反馈都是我前进路上的宝贵财富。同时,我也非常期待能够得到你的点赞、收藏,关注这将是对我莫大的支持和鼓励。当然,我更期待的是能够持续为你带来有价值的内容,让我们在知识的道路上共同前行。