preface
pthread_create(&tid, &a, start_routine, (void *)pt)
第四个参数 `pt` 里可能确实有自己的类方法,
那为什么不直接让线程执行 `pt` 的类方法,
而还要传第三个参数 `start_routine` 呢?
详细版的解释
简单说:
pthread_create是 C 接口,它只认识一种固定格式的“线程入口函数”。
第三个参数告诉系统:新线程从哪里开始执行。
第四个参数只是传给这个入口函数的一份数据/对象指针,本身不会被自动执行。
1. pthread_create 的本质
函数原型大概是:
int pthread_create(
pthread_t *thread,
const pthread_attr_t *attr,
void *(*start_routine)(void *),
void *arg
);
第三个参数要求是一个函数指针:
void *(*start_routine)(void *)
也就是说,它必须是这种形式的函数:
void *func(void *arg);
所以:
pthread_create(&tid, &a, start_routine, (void *)pt);
意思是:
新线程启动后,执行:
start_routine((void *)pt);
注意,不是执行 pt,而是执行 start_routine,并把 pt 传进去。
2. 第四个参数只是“参数”,不是“入口”
可以把它理解成普通函数调用:
start_routine((void *)pt);
这里:
start_routine是函数名,也就是要执行的代码入口;(void *)pt是传进去的参数。
就像你调用:
printf("hello");
你不会说 "hello" 自己会执行。
"hello" 只是被 printf 使用的数据。
同理:
pthread_create(&tid, &a, start_routine, (void *)pt);
里面的 pt 只是传递给 start_routine 的数据。
3. 那 pt 有类方法,为什么不能直接执行?
假设你有一个类:
class MyThread {
public:
void run() {
// 真正的线程逻辑
}
};
然后:
MyThread *pt = new MyThread();
pt 是一个对象指针,它可以调用:
pt->run();
但问题是:pthread_create 需要的是这种函数:
void *func(void *);
而普通类成员函数 MyThread::run 并不是这种函数。
4. 普通类成员函数有隐藏的 this
比如:
pt->run();
看起来没有参数,但编译器内部其实类似:
MyThread::run(pt);
也就是说,普通成员函数隐含带了一个 this 指针。
所以成员函数:
void MyThread::run();
它的真实调用依赖对象:
pt->run();
而 pthread_create 不能直接理解 C++ 成员函数的这种调用方式。它只知道:
void *func(void *);
所以不能直接这样写:
pthread_create(&tid, &a, pt->run, nullptr); // 错误
也不能直接这样:
pthread_create(&tid, &a, MyThread::run, pt); // 普通成员函数不匹配
5. start_routine 的作用:中转函数 / 包装函数
通常 start_routine 的作用就是把 void * 转回你的类对象,然后调用对象自己的方法。
例如:
class MyThread {
public:
void run() {
// 真正在线程中执行的代码
}
};
void *start_routine(void *arg)
{
MyThread *pt = static_cast<MyThread *>(arg);
pt->run();
return nullptr;
}
然后创建线程:
MyThread *pt = new MyThread();
pthread_create(&tid, nullptr, start_routine, (void *)pt);
执行流程是:
新线程开始
↓
调用 start_routine(pt)
↓
在 start_routine里面把 pt 转回 MyThread*
↓
调用 pt->run()
↓
真正执行类对象的线程逻辑
所以第三个参数不是多余的,它是线程入口。第四个参数只是让这个入口函数知道“要操作哪个对象”。
6. 可以理解成“入口函数”和“业务对象”的区别
第三个参数 start_routine:
void *start_routine(void *arg)
负责:
- 符合
pthread_create要求的函数格式; - 作为新线程的启动入口;
- 接收
void *参数; - 把参数转换成真实类型;
- 调用对象自己的方法。
第四个参数 pt:
(void *)pt
负责:
- 告诉
start_routine操作哪个对象; - 保存类对象的状态、成员变量、成员函数等;
- 作为上下文传入新线程。
7. 为什么系统不能自动调用 pt 的方法?
因为系统不知道你想调用哪个方法。
比如 pt 里面可能有很多方法:
class MyThread {
public:
void init();
void run();
void stop();
void reset();
void process();
};
你传了一个 pt 给线程库,线程库怎么知道你想执行:
pt->run();
还是:
pt->process();
还是:
pt->init();
它不知道。
所以必须明确告诉它一个入口函数:
start_routine
然后你在这个入口函数里面决定调用哪个类方法。
8. 如果想更像 C++,可以用静态成员函数
常见写法是把线程入口写成类的 static 方法:
class MyThread {
public:
void run() {
// 线程实际逻辑
}
static void *threadEntry(void *arg) {
MyThread *self = static_cast<MyThread *>(arg);
self->run();
return nullptr;
}
};
创建线程:
MyThread *pt = new MyThread();
pthread_create(&tid, nullptr, MyThread::threadEntry, pt);
这里 threadEntry 是静态成员函数,没有隐藏的 this 指针,所以它可以当作普通函数指针传给 pthread_create。
9. 总结一句话
pthread_create 的第三个参数是“线程从哪里开始执行”,第四个参数是“传给这个入口函数的数据”。
虽然第四个参数 pt 是对象,里面有类方法,但它本身只是一个地址,不会被线程自动执行。你必须通过第三个参数 start_routine 这个入口函数,把 pt 转回对象,然后显式调用它的方法,例如:
pt->run();
所以典型结构就是:
void *start_routine(void *arg) {
MyThread *pt = static_cast<MyThread *>(arg);
pt->run();
return nullptr;
}
可以把 start_routine 理解成:pthread 世界和 C++ 对象世界之间的桥梁。
二、用一个比喻来解释:
核心区别:数据 vs 执行逻辑
start_routine 是线程的入口点,pt 是被操作的对象。
类比理解
想象你雇了一个工人(新线程) 去操作一台机器(pt对象):
pt(第四个参数)= 那台机器,它有自己的各种功能按钮(类方法)start_routine(第三个参数)= 工人的工作流程手册,规定工人要按什么顺序、在什么时机去按哪些按钮
工人不会自动知道该做什么,必须有人告诉他工作流程。
技术层面的原因
pthread 是 C 语言的 API,它不认识 C++ 对象、不懂类方法,它只接受一个普通函数指针作为线程入口。所以需要 start_routine 这个"桥梁函数"来:
新线程启动
↓
执行 start_routine(void* pt) ← 线程必须有个"从哪里开始跑"的地方
↓
在函数内部:MyClass* obj = (MyClass*)pt; ← 还原真实类型
↓
调用 obj->doWork(); ← 现在才真正调用对象自己的方法
实际代码示意
// 第三个参数:线程入口函数(C风格,必须是普通函数)
void* start_routine(void* arg) {
// 在这里决定"怎么用"第四个参数
MyClass* pt = (MyClass*)arg; // 还原类型
// 控制执行顺序、加锁、循环等逻辑
while (pt->isRunning()) {
pt->processData(); // 调用pt自己的方法
pt->updateStatus();
sleep(1);
}
return nullptr;
}
一句话总结
| 参数 | 角色 | 解决的问题 |
|---|---|---|
start_routine | “怎么做” — 执行逻辑、流程控制 | 线程从哪里开始、按什么顺序做事 |
pt | “对谁做” — 数据和能力的载体 | 操作哪个对象、用哪些数据 |
pt 虽然有自己的方法,但它不知道自己应该在新线程里按什么顺序、在什么条件下被调用——这正是 start_routine 存在的意义。

58

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



