线程的创建(pthread_create)
pthread_t tid;//本质是unsigned long类型,打印时得到的是该线程的虚拟地址
int pthread_create(
pthread_t *thread,
const pthread_attr_t *attr,
void *(*start_routine)(void*),
void *arg
);
- pthread_t *thread:存储新线程的标识符(ID)
- const pthread_attr_t *attr:线程属性(如栈大小等)一般为 nullptr 使用默认属性
- void *(*start_routine)(void*):指向线程要执行的函数(返回值和参数类型都要是void*)
- void *arg:传递给线程调用的函数的参数(可以是字符串、整型、类对象等)
- 返回值:线程的所有相关函数执行成功时均返回0,失败时均返回错误码
#include <iostream>
#include <string>
#include <cstring>
#include <pthread.h>
#include <unistd.h>
class threadData {
public:
std::string name;
int num;
};
// 线程函数:传递字符串
void* threadrun1(void* arg) {
const char* name = static_cast<const char*>(arg);
int cnt = 10;
while (cnt--) {
std::cout << "name = " << name << ", cnt = " << cnt << std::endl;
sleep(1);
}
return nullptr;
}
// 线程函数:传递整型
void* threadrun2(void* arg) {
int num = *static_cast<int*>(arg);
int cnt = 10;
while (cnt--) {
std::cout << "num = " << num << ", cnt = " << cnt << std::endl;
sleep(1);
}
return nullptr;
}
// 线程函数:传递类对象(堆分配)
void* threadrun3(void* arg) {
threadData* data = static_cast<threadData*>(arg);
int cnt = data->num;
while (cnt--) {
std::cout << "[ClassThread] Name: " << data->name
<< ", Count: " << cnt << std::endl;
sleep(1);
}
delete data; // 释放堆内存
return nullptr;
}
int main() {
pthread_t tid1, tid2, tid3;
// 动态分配类对象(堆内存)
threadData* myData = new threadData(); // 使用 new 分配
myData->name = "ClassThread";
myData->num = 8;
// 创建线程
int n1 = pthread_create(&tid1, nullptr, threadrun1, (void*)"thread 1");
int num_arg = 42;
int n2 = pthread_create(&tid2, nullptr, threadrun2, (void*)&num_arg);
int n3 = pthread_create(&tid3, nullptr, threadrun3, (void*)myData); // 传递堆对象指针
// 错误检查
if (n1 || n2 || n3) {
int error_code = n1 ? n1 : (n2 ? n2 : n3);
std::cerr << "线程创建失败: " << strerror(error_code) << std::endl;
delete myData; // 错误时释放堆内存
return 1;
}
// 等待线程结束
pthread_join(tid1, nullptr);
pthread_join(tid2, nullptr);
pthread_join(tid3, nullptr);
// 注意:myData 已在 threadrun3 中被释放,此处无需重复 delete
std::cout << "\n所有线程执行完毕,主线程退出" << std::endl;
return 0;
}
注意事项:多线程编程中,虽然每个线程有独立的栈空间,但主线程传递给子线程的类对象或结构体对象仍应通过 new
在堆上动态分配:
- 虽然栈空间独立,但整个进程的虚拟地址空间是共享的,因此线程间可通过指针访问任意内存地址(包括其他线程的栈)
- 主线程如果以
pthread_exit的方式退出,那么主线程栈上的对象会被销毁,此时子线程再访问已销毁的栈对象会导致悬空指针问题,而如果是堆对象的话
只要子线程运行期间该对象未被释放,就可以避免悬垂指针(若主线程不以pthread_exit的方式退出时所有线程都退出就不会出现悬空指针问题
)
线程的等待(pthread_join)
// 线程执行的函数:传递类对象(堆分配)
void* threadrun3(void* arg) {
threadData* data = static_cast<threadData*>(arg);
int cnt = data->num;
while (cnt--) {
std::cout << "[ClassThread] Name: " << data->name
<< ", Count: " << cnt << std::endl;
sleep(1);
}
delete data; // 释放堆内存
return (void*)111;//返回线程执行完该函数后的信息
} |
| //返回信息放入ret中
V
void* ret = nullptr;//ret是指针变量,被分配了空间,使用pthread_join时都要有一个ret这种的一级指针存放返回信息
|_____________________________
|
V
int pthread_join(pthread_t thread, void **retval);//pthread_join函数的原始状态
||
int pthread_join(tid,&ret);//实际中会写成
pthread_t thread:
目标线程的标识符(pthread_t
类型)void **retval:输出型参数,二级
指针,用于存储目标线程的退出状态(可为NULL
)- ret的类型不一定是void*可根据线程函数的返回值决定,但是pthread_join的第二个参数的类型必须是void**,如果不是需要强转
- 若多个线程尝试
pthread_join
同一个线程,会引发未定义行为
线程函数的返回值
基本概念:线程的返回值可以是整型、字符串也可以是对象,且必须提供void*类型的返回值
#include <iostream>
#include <string>
#include <cstring>
#include <pthread.h>
#include <unistd.h>
//线程执行函数
class threadData {
public:
int Task()
{
int result = x + y;
return result;
}
int x;
int y;
int result;
};
//线程结果查询函数
class threadResult
{
public:
std::string print()
{
return std::to_string(x) + "+" + std::to_string(y) + " = " + std::to_string(result);//这里的+表示追加字符串
}
public:
int result;
int x;
int y;
};
// 线程函数:传递类对象(堆分配)
void* threadrun(void* arg) {
threadData* data = static_cast<threadData*>(arg);
threadResult* result = new threadResult();
int cnt = 3;
while(cnt)
{
sleep(3);
std::cout <<" run ... , cnt: " << cnt-- <<std::endl;
result->result = data->Task();
result->x = data->x;
result->y = data->y;
}
delete data; // 释放堆内存
return (void*)result;
}
int main() {
pthread_t tid;
// 动态分配类对象(堆内存)
threadData* myData = new threadData(); // 使用 new 分配
myData->x = 1;
myData->y = 2;
// 创建线程
int n = pthread_create(&tid, nullptr, threadrun, (void*)myData); // 传递堆对象指针
// 错误检查
if (n != 0) {
std::cerr << "线程创建失败: " << strerror(n) << std::endl;
delete myData; // 错误时释放堆内存
return 1;
}
// 等待线程结束
threadResult* result = nullptr;
int m = pthread_join(tid, (void**)&result);
if (m == 0 && result != nullptr) {
std::cout << "子线程结果: " << result->print() << std::endl;
delete result; // 主线程负责释放结果
} else {
std::cerr << "线程执行失败或未返回结果" << std::endl;
}
return 0;
}
多线程的创建
#include <iostream>
#include <vector>
#include <unistd.h>
//打印线程地址
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;//返回线程名
}
const int num = 10;
int main()
{
std::vector<pthread_t> tids;
//创建多线程
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);//格式化向name指向的缓冲区中打印
pthread_create(&tid,nullptr,ThreadRun,name /*线程的名字*/);
//3、保存所有线程的id消息
tids.emplace_back(tid);//使用emplace_back而不是push_back向数组中插入,避免了临时对象的出现
}
//主线程等待所有线程跑完
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;
}
return 0;
}
┌───────────────────────┐
│ 主线程流程 │
└──────────┬────────────┘
│
▼
┌───────────────────────┐
│ 创建线程ID容器 vector │
└──────────┬────────────┘
│
▼
┌───────────────────────┐
│ 循环创建10个子线程 │
│ ┌─────────┐ │
│ │i=0~9 │ │
│ ├────┬────┤ │
│ │分配name│ │
│ │内存(new char[128])│
│ ├────┼────┤ │
│ │格式化线程名 │
│ │snprintf(thread-X) │
│ ├────┼────┤ │
│ │创建线程 │
│ │pthread_create() │
│ ├────┼────┤ │
│ │保存tid到容器 │
│ │tids.emplace_back() │
│ └────┴────┘ │
└──────────┬────────────┘
│
▼
┌───────────────────────┐
│ 等待所有子线程完成 │
│ ┌─────────┐ │
│ │遍历tids容器│ │
│ ├────┬────┤ │
│ │等待线程 │ │
│ │pthread_join() │
│ ├────┼────┤ │
│ │获取返回指针 │
│ │void* name │
│ ├────┼────┤ │
│ │打印退出信息 │
│ │cout << name │
│ ├────┼────┤ │
│ │释放内存 │
│ │delete[] name │
│ └────┴────┘ │
└──────────┬────────────┘
│
▼
┌───────────────────────┐
│ 主线程退出 │
└───────────────────────┘
线程的终止与取消(pthread_exit、pthread_cancel)
//调用此函数后,当前线程终止,retval 会被传递给 pthread_join 的接收参数(类似于线程调用函数的返回值)
void pthread_exit(void *retval);
int pthread_cancel(pthread_t thread);//不常用
- void *retval:线程退出时返回的指针
pthread_t thread:
目标线程的标识符
注意事项:
1、主线程最好最后一个结束,防止主线程结束后导致的所有线程退出的问题
2、子线程的退出标志是其所调用的函数return了
3、exit()用于终止进程,如果在多线程时调用exit()会导致当前进程结束,所有主线程退出
4、pthread_exit()用于终止线程 == 子线程调用函数中的return
#include <iostream>
#include <pthread.h>
#include <unistd.h>
void* thread_func(void* arg) {
int* result = malloc(sizeof(int));
*result = 42;
pthread_exit(result); // 返回堆内存指针(充当函数的返回值)
}
int main() {
pthread_t tid;
pthread_create(&tid, nullptr, thread_func, NULL);
void* retval;
pthread_join(tid, &retval);
printf("Result: %d\n", *(int*)retval); // 输出 42
free(retval); // 必须手动释放
}
5、主线程使用pthread_exit退出后,子线程还可以继续运行,但退出位置的后续代码不能执行
#include <iostream>
#include <pthread.h>
#include <unistd.h>
void* thread_func(void* args)
{...} <——————————————————————————————————————————————————
|
int main() { |
pthread_t tid; |
pthread_create(&tid, NULL, thread_func, NULL); |
pthread_exit(NULL); // 主线程退出但子线程继续运行; ————
// 后续代码不会执行
}
6、 pthread_cancel退出线程时,该线程的返回信息是-1
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
void* thread_func(void* arg) {
while (true) {
printf("线程运行中...\n");
sleep(1); // 取消点(sleep 是取消点函数)
}
return NULL;
}
int main() {
pthread_t tid;
pthread_create(&tid, NULL, thread_func, NULL);
sleep(2); // 等待线程运行
// 发送取消请求
pthread_cancel(tid);
void* retval;
pthread_join(tid, &retval);
// 检查返回值
if (retval == PTHREAD_CANCELED) {
printf("线程被取消!返回值地址: %p\n", retval);
} else {
printf("线程正常退出,返回值: %p\n", retval);
}
return 0;
}
线程的分离(pthread_detach)
int pthread_detach(pthread_t thread);
- pthread_t thread:目标线程的标识符
适用场景:当线程无需返回数据,且主线程无需等待其结束时
功能:被分离线程终止后,系统会自动回收其栈和线程描述符,避免资源泄漏和僵尸线程的出现
注意事项:
1、若线程已经调用pthread_detach,则不能再调用pthread_join获取该线程的返回信息,否则线程退出并报错
2、若不分离且未调用 pthread_join
,线程终止后仍占用资源,成为“僵尸线程”
3、主线程无需等待被分离的线程结束,且子线程调用的函数中传入pthread_self()可自行分离,且常常需要资源安全验证和同步机制
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
// 线程数据结构体(需动态分配)
typedef struct {
int id;
char* task_name;
} ThreadData;
// 资源清理函数
void cleanup(void* arg) {
ThreadData* data = (ThreadData*)arg;
printf("[清理] 释放资源: Task%d (%s)\n", data->id, data->task_name);
free(data->task_name);
free(data);
}
// 线程函数:子线程自行分离 + 资源安全验证
void* thread_func(void* arg) {
// 注册清理函数(确保资源释放)
pthread_cleanup_push(cleanup, arg);
// 尝试自行分离(防御性编程)
int detach_ret = pthread_detach(pthread_self());
if (detach_ret != 0) {
fprintf(stderr, "[子线程] 分离失败: %s\n", strerror(detach_ret));
}
ThreadData* data = (ThreadData*)arg;
printf("[子线程] Task%d 执行: %s\n", data->id, data->task_name);
sleep(2); // 模拟耗时操作
// 正常退出时手动触发清理(参数0表示不执行清理)
pthread_cleanup_pop(0);
return NULL;
}
int main() {
pthread_t tid;
// 动态分配线程数据(需确保资源安全)
ThreadData* data = malloc(sizeof(ThreadData));
data->id = 1;
data->task_name = strdup("ProcessData");
// 创建线程
int create_ret = pthread_create(&tid, NULL, thread_func, data);
if (create_ret != 0) {
fprintf(stderr, "[主线程] 创建失败: %s\n", strerror(create_ret));
free(data->task_name);
free(data);
return 1;
}
// 主线程尝试分离子线程(双重保障)
int detach_ret = pthread_detach(tid);
if (detach_ret != 0) {
fprintf(stderr, "[主线程] 分离失败: %s\n", strerror(detach_ret));
}
printf("[主线程] 继续运行,不等待子线程\n");
// 等待足够时间确保子线程完成(实际项目需条件变量同步)
sleep(3); // 必须大于子线程的 sleep(2)
return 0;
}
4、pthread_detach的返回值有多种不同的错误码
int ret = pthread_detach(tid);
if (ret == EINVAL) {
fprintf(stderr, "线程已分离或不存在\n");
} else if (ret == ESRCH) {
fprintf(stderr, "线程ID无效\n");
}
~over~