线程的创建与生命周期:POSIX线程创建、C++11 std::thread、Python threading模块、线程状态转换
说到多线程编程,第一个要面对的问题就是:线程怎么生出来?又怎么结束?
我刚开始接触多线程时,觉得这玩意儿挺玄乎。后来踩了几个坑才明白,线程的创建和生命周期管理,其实是整个并发编程的基石。你想想看,如果连线程怎么创建、怎么销毁都搞不清楚,后面那些锁啊、条件变量啊,根本无从谈起。
今天我们就来聊聊三种主流的多线程创建方式:POSIX线程(pthread)、C++11的std::thread,以及Python的threading模块。顺便把线程的状态转换也捋清楚。
POSIX线程:最底层的创建方式
POSIX线程,也就是pthread,是Linux/Unix系统上最原生的线程API。我当年在嵌入式项目中第一次用pthread,说实话,接口有点粗糙,但胜在灵活。
创建线程的核心函数是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:线程属性,比如栈大小、调度策略。传NULL就用默认值
- start_routine:线程入口函数,返回void*,参数也是void*
- arg:传给入口函数的参数
举个简单的例子:
#include <pthread.h>
#include <stdio.h>
void* worker(void* arg) {
int id = *(int*)arg;
printf("Worker thread %d is running\n", id);
return NULL;
}
int main() {
pthread_t t1, t2;
int id1 = 1, id2 = 2;
pthread_create(&t1, NULL, worker, &id1);
pthread_create(&t2, NULL, worker, &id2);
pthread_join(t1, NULL);
pthread_join(t2, NULL);
return 0;
}
线程创建后,主线程需要用pthread_join等待它结束,或者用pthread_detach把它分离。分离后的线程就像脱缰的野马,结束时会自动释放资源,你不能再join它。
C++11 std::thread:现代C++的优雅方案
C++11引入了std::thread,终于不用再跟pthread那堆C风格的API死磕了。我个人习惯用std::thread,因为它更安全、更直观。
创建方式很简单:
#include <thread>
#include <iostream>
void worker(int id) {
std::cout << "Thread " << id << " is running\n";
}
int main() {
std::thread t1(worker, 1);
std::thread t2(worker, 2);
t1.join();
t2.join();
return 0;
}
你看,参数直接传,不用包装成void*。而且std::thread支持lambda表达式,写起来更灵活:
std::thread t([](int id) {
std::cout << "Lambda thread " << id << "\n";
}, 42);
t.join();
我建议用RAII包装一下std::thread,比如写个ThreadGuard类,在析构时自动join。这样就算有异常抛出,线程也能安全结束。
Python threading模块:简单但要注意GIL
Python的threading模块,说实话,用起来最舒服。但有个大坑——GIL(全局解释器锁)。
创建线程的代码极其简洁:
import threading
import time
def worker(name):
print(f"Thread {name} started")
time.sleep(1)
print(f"Thread {name} finished")
t1 = threading.Thread(target=worker, args=("A",))
t2 = threading.Thread(target=worker, args=("B",))
t1.start()
t2.start()
t1.join()
t2.join()
Python的线程是真正的操作系统线程,但GIL保证同一时刻只有一个线程在执行Python字节码。所以对于CPU密集型任务,Python多线程其实没啥用——说白了就是伪并行。
我在项目中遇到过一个问题:用Python多线程做图像处理,结果8个核只跑满了一个。后来换成multiprocessing,速度直接翻了6倍。嗯,选对工具很重要。
线程状态转换:从生到死
线程不是一创建就开始运行的。它有一个完整的生命周期。我画了一张图,帮你理清这几种状态:
这张图展示了线程的五个核心状态:
| 状态 | 说明 | 触发条件 |
|---|---|---|
| 新建 (New) | 线程对象已创建,但还没开始执行 | 调用pthread_create、std::thread构造、Thread.start()之前 |
| 就绪 (Ready) | 线程可以被调度执行,但还没轮到它 | 调用start()后,等待操作系统调度 |
| 运行 (Running) | 线程正在CPU上执行代码 | 操作系统调度器选中该线程 |
| 阻塞 (Blocked) | 线程因为某些原因暂停执行 | 等待I/O、获取锁、sleep、条件变量等待 |
| 终止 (Terminated) | 线程执行完毕或被强制结束 | 入口函数返回、调用pthread_exit、异常退出 |
我在项目中遇到过一个问题:一个线程在等待锁的时候,另一个线程持有锁但被阻塞在I/O上,结果两个线程都卡死了。这就是典型的死锁场景。后来我加了超时机制,才把问题解决。
关于线程的生命周期,还有几点要记住:
- 线程一旦终止,不能重新启动。想再用,只能新建一个线程对象
- 主线程退出时,子线程的行为取决于设置。pthread中默认是主线程退出后进程结束,所有子线程被强制终止。std::thread和Python threading中,主线程会等待所有非daemon线程结束
- daemon线程(Python)或detach线程(C++):主线程退出时,这些线程会被直接终止。适合后台任务,但要注意资源清理
好了,线程的创建和生命周期就聊到这里。三种方式各有优劣:pthread最底层但最灵活,std::thread最现代最安全,Python threading最简单但受限于GIL。选择哪种,取决于你的场景和语言生态。
记住一点:线程不是越多越好。线程的创建和切换都有开销。我见过有人为了处理100个请求开了100个线程,结果性能还不如用线程池。嗯,适度才是王道。