Linux 线程学习总结:创建、等待、退出、分离(含实战代码与避坑)

前言

这段时间通过手写四段核心代码,系统学习了 Linux 下 POSIX 线程的基本使用,包括线程创建、线程等待、线程退出、线程分离四大核心知识点。本文基于自己写的代码、遇到的问题、理解误区,做一次完整总结,方便后续复习回顾与面试使用。

一、线程基本概念

  1. 线程是进程内部的执行流,共享进程地址空间。 一个进程可以创建多个线程,轻量级、切换成本低。
  2. 线程共享:代码段、数据段、文件描述符、全局变量等。
  3. 线程独有:栈、上下文寄存器、信号屏蔽字、线程局部存储。
  4. 新创建的线程默认是joinable 状态,必须被等待,否则资源泄漏。

二、线程创建 —— pthread_create

这是最基础的 API,用于创建一个新线程。

int pthread_create(
    pthread_t *thread,
    const pthread_attr_t *attr,
    void *(*start_routine)(void *),
    void *arg
);

参数说明
• thread:输出型参数,返回线程 ID
• attr:线程属性,NULL 表示使用默认属性
• start_routine:线程入口函数
• arg:传递给线程的参数
返回值
成功返回 0,失败返回错误码。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>

void *rout(void *arg)
{
    for (;;)
    {
        printf("I'am thread 1\n");
        sleep(1);
    }
}

int main(void)
{
    pthread_t tid;
    int ret;

    if ((ret = pthread_create(&tid, NULL, rout, NULL)) != 0)
    {
        fprintf(stderr, "pthread_create: %s\n", strerror(ret));
        exit(EXIT_FAILURE);
    }

    for (;;)
    {
        printf("I'am main thread\n");
        sleep(1);
    }
}

注意事项
编译必须加 -pthread,否则会出现链接错误。
线程创建后与主线程并发运行。
主线程如果提前退出,整个进程会结束,所有线程一起终止。

三、线程等待 —— pthread_join

为什么需要线程等待?

  1. 默认线程是 joinable,(新创建出来的线程,默认就是 joinable 状态。
    意思就是:这个线程必须被人 “回收”,否则会产生内存泄漏!)退出后资源不会自动释放,会造成泄漏。
  2. 已退出线程的空间仍留在进程地址空间。
  3. 新线程不会复用旧线程资源。
  4. 可以获取线程退出返回值。
    函数原型
int pthread_join(pthread_t thread, void **value_ptr);

功能
阻塞等待指定线程结束
回收线程内核资源
获取线程退出码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
/*
为什么需要线程等待?
• 已经退出的线程,其空间没有被释放,仍然在进程的地址空间内。
• 创建新的线程不会复⽤刚才退出线程的地址空间。
功能:等待线程结束
原型
 int pthread_join(pthread_t thread, void **value_ptr);

参数:
 thread:线程ID
 value_ptr:它指向⼀个指针,后者指向线程的返回值

返回值:成功返回0;失败返回错误码
*/
void *thread1(void *arg)
{
    printf("thread 1 returning ... \n");
    // 这里我创建一个内存资源,在堆上创建,需要手动释放
    int *p = (int *)malloc(sizeof(int));
    *p = 1;
    // return:函数返回,线程也结束
    return (void *)p;
}
void *thread2(void *arg)
{
    printf("thread 2 exiting ...\n");
    // 同上
    int *p = (int *)malloc(sizeof(int));
    *p = 2;
    /*
    只能自己调用自己
退出后线程资源不会立刻释放,要等别人 join
主线程用它,不会结束整个进程,只会结束主线程自己

    */
    pthread_exit((void *)p);
}
void *thread3(void *arg)
{
    while (1)
    { //
        printf("thread 3 is running ...\n");
        sleep(1);
    }
    return NULL;
}
int main(void)
{
    pthread_t tid;
    void *ret;
    // thread 1 return
    pthread_create(&tid, NULL, thread1, NULL);
    // 需要手动释放线程资源,然后这里等待线程退出
    pthread_join(tid, &ret);
    printf("thread return, thread id %lx, return code:%d\n", tid, *(int *)ret);
    // 需要手动释放
    free(ret);
    // thread 2 exit
    // 也是一样
    pthread_create(&tid, NULL, thread2, NULL);
    // 等待退出
    pthread_join(tid, &ret);
    printf("thread return, thread id %lx, return code:%d\n", tid, *(int *)ret);
    free(ret);

    // thread 3 cancel by other
    pthread_create(&tid, NULL, thread3, NULL);
    sleep(3);
    // 取消线程,需要join
    pthread_cancel(tid);
    pthread_join(tid, &ret);
    if (ret == PTHREAD_CANCELED)
        printf("thread return, thread id %lx, return code:PTHREAD_CANCELED\n", tid);
    else
        printf("thread return, thread id %lx, return code:NULL\n", tid);
}

关键点
join 会阻塞,直到线程退出。
线程返回的堆空间需要手动 free。
不 join 一定会导致线程资源泄漏。

四、线程退出的三种方式

线程退出有三种典型方式,行为略有差异,面试高频考点。

  1. return 退出
    在线程函数中 return,线程正常结束。效果等价于 pthread_exit。
return (void *)p;
  1. pthread_exit 主动退出
    线程自己终止自己,不会结束整个进程
pthread_exit((void *)p);

特点:
只能自己调用自己
立即终止线程
主线程调用不会杀死整个进程
3. pthread_cancel 被其他线程取消
一个线程取消另一个线程。

pthread_cancel(tid2);

特点:
不是立即杀死线程
必须到达取消点才真正退出
取消点如:sleep、printf、cout、read、write 等
被取消线程的退出码是 PTHREAD_CANCELED

五、线程分离 —— pthread_detach

为什么要分离?

joinable 线程必须 join,否则泄漏。
若不关心返回值,join 是多余负担。
分离线程退出时系统自动释放资源,无需 join。
使用方式

pthread_detach(pthread_self());

特点:
分离后不能再被 pthread_join
join 会直接失败
线程退出自动释放资源
适合后台服务线程、工作线程

#include <iostream>
#include <unistd.h>
#include <string.h>
/*

分离线程
• 默认情况下,新创建的线程是joinable的,线程退出后,需要对其进⾏pthread_join操作,否则
⽆法释放资源,从⽽造成系统泄漏。
• 如果不关⼼线程的返回值,join是⼀种负担,这个时候,我们可以告诉系统,当线程退出时,⾃
动释放线程资源。
*/

#include <pthread.h>
void *thread_run(void *arg)
{
    // 我自己分离出去,不要主线程来释放我了,我没有返回值,所不会造成资源泄漏
    pthread_detach(pthread_self());
    printf("%s\n", (char *)arg);
    return NULL;
}
int main(void)
{
    pthread_t tid;
    if (pthread_create(&tid, NULL, thread_run, (void *)"thread1 run...") != 0)
    {
        printf("create thread error\n");
        return 1;
    }
    int ret = 0;
    sleep(1);
    // 让主线程等子线程分离,否则主线程跑太快了,子线程还没分离呢,就让主线程等到了,就直接给释放了
    if (pthread_join(tid, NULL) == 0)
    {
        printf("pthread wait success\n");
        ret = 0;
    }
    // 然后子线程分离了,主线程就等待失败了
    else
    {
        printf("pthread wait failed\n");
        ret = 1;
    }
    return ret;
}

运行结果必然是 join 失败。

六、joinable 与 detached 对比总结

joinable(默认)
• 需要 pthread_join
• 可获取返回值
• 不 join → 资源泄漏
detached(分离状态)
• 无需 join
• 自动释放资源
• 不能被 join
• 适合后台长期运行线程

七、线程 API 速记

pthread_create 创建线程 成功返回 0
pthread_join 等待并回收线程 阻塞、获取返回值
pthread_exit 线程主动退出 不终止进程
pthread_cancel 取消另一个线程 依赖取消点
pthread_detach 设置线程分离 自动释放资源
pthread_self 获取自身线程 ID 常用

八、总结

线程默认 joinable,必须 join,否则泄漏。
线程退出三种方式:return、pthread_exit、pthread_cancel。
pthread_cancel 不是立即杀死线程,必须到达取消点。
线程不要返回局部变量地址。
分离线程自动释放资源,无需 join。
主线程不能提前退出,否则所有线程一起结束。
编译必须加 -pthread。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐