线程的创建与生命周期: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;
}
注意:传给线程的参数(比如上面的id1、id2)必须保证在线程使用期间有效。我曾经犯过一个错误——在循环中创建线程时传了同一个变量的地址,结果所有线程读到的都是最后一个值。嗯,血的教训。

线程创建后,主线程需要用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();
小技巧:std::thread对象在析构时如果线程还在运行(既没join也没detach),程序会直接terminate。所以要么join,要么detach,别留烂摊子。

我建议用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多线程适合I/O密集型任务(网络请求、文件读写),因为I/O操作会释放GIL。CPU密集型任务请用多进程(multiprocessing模块)。

我在项目中遇到过一个问题:用Python多线程做图像处理,结果8个核只跑满了一个。后来换成multiprocessing,速度直接翻了6倍。嗯,选对工具很重要。

线程状态转换:从生到死

线程不是一创建就开始运行的。它有一个完整的生命周期。我画了一张图,帮你理清这几种状态:

新建 (New) 就绪 (Ready) 运行 (Running) 阻塞 (Blocked) 终止 (Terminated) start() 调度 时间片用完/主动yield I/O、锁、sleep 条件满足 return/exit

这张图展示了线程的五个核心状态:

状态 说明 触发条件
新建 (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++):主线程退出时,这些线程会被直接终止。适合后台任务,但要注意资源清理
我的建议:尽量用join而不是detach。detach的线程就像没人管的孩子,出了问题很难排查。除非你非常确定这个线程的生命周期不会造成问题。

好了,线程的创建和生命周期就聊到这里。三种方式各有优劣:pthread最底层但最灵活,std::thread最现代最安全,Python threading最简单但受限于GIL。选择哪种,取决于你的场景和语言生态。

记住一点:线程不是越多越好。线程的创建和切换都有开销。我见过有人为了处理100个请求开了100个线程,结果性能还不如用线程池。嗯,适度才是王道。

可可精品资料库