Linux:线程控制

发布于:2025-09-13 ⋅ 阅读:(21) ⋅ 点赞:(0)


1. POSIX 线程库概述

POSIX 线程库提供了一系列以 pthread_ 开头的函数,用于线程的创建、终止、同步与控制。使用 pthread 库的基本步骤:

  1. 引入头文件:
#include <pthread.h>
  1. 编译时链接 pthread 库:
g++ main.cpp -o main -lpthread

这里的 -lpthread 告诉编译器链接 pthread 库。


2. 创建线程

线程创建的核心函数是 pthread_create

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,失败返回错误码。

示例

void *routine(void *buf)
{
    std::string str = static_cast<char *>(buf);

    int cnt = 5;
    while (cnt--)
    {
        std::cout << "我是子线程:" << str << std::endl;
    }
    return (void *)100;
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, routine, (void *)"thread-1");

    int cnt = 5;
    while (cnt--)
    {
        std::cout << "我是主线程" << std::endl;
    }

    void *ret;
    pthread_join(tid, &ret);
    std::cout << "线程返回值:" << (long)ret << std::endl;

    return 0;
}

在这里插入图片描述


3. 获取线程 ID

POSIX 提供了 pthread_self 函数获取当前线程 ID:

pthread_t pthread_self(void);

pthread_t 是线程库定义的类型,用于唯一标识进程内的线程。它是进程内唯一的标识,由 pthread 库维护,和操作系统内核的线程 ID 不完全一样。通过 ps -aL 可以查看系统层面的线程 ID(LWP)。

$ ps -aL | grep mythread

4. 线程传参与返回值详解

示例代码

#include <iostream>
#include <pthread.h>

// 任务类
class Test {
public:
    Test(int a, int b) : _a(a), _b(b) {}
    int add() { return _a + _b; }
    ~Test() {}
private:
    int _a;
    int _b;
};

// 返回值封装类
class Result {
public:
    Result(int ret) : _ret(ret) {}
    int getRet() { return _ret; }
    ~Result() {}
private:
    int _ret;
};

// 线程执行函数
void *routine(void *args) {
    Test* t = static_cast<Test*>(args); // 转回 Test*
    int ret = t->add();                 // 执行计算
    Result* r = new Result(ret);        // 封装返回值
    delete t;                           // 释放任务对象
    return (void *)r;                   // 返回给主线程
}

int main() {
    Test *t = new Test(10, 20);        // 在堆上分配任务对象

    pthread_t tid;
    pthread_create(&tid, nullptr, routine, (void *)t); // 传递任务指针

    Result* ret = nullptr;
    pthread_join(tid, (void **)&ret);  // 获取线程返回值

    std::cout << "线程返回值:" << ret->getRet() << std::endl;

    delete ret;                        // 释放返回值对象
    return 0;
}

在这里插入图片描述


1. 线程参数传递:任意类型

原理:

  • pthread_create 的第四个参数是 void* arg,可以传任意类型的指针。
  • 在线程函数内部,我们通过类型转换将 void* 转回原始类型。
  • 参数对象最好在堆上分配,避免栈对象在主线程作用域结束后被释放。

代码解析:

Test *t = new Test(10, 20);               // 堆上分配任务对象
pthread_create(&tid, nullptr, routine, (void *)t); // 将任务对象指针传入线程
  • 这里将 Test* 转换为 void* 传入线程。
  • 在线程函数中,再用 static_cast 转回 Test*
Test* t = static_cast<Test*>(args);

✅ 注意:static_cast 是类型安全的转换,确保线程内可以正确访问对象。


2. 线程返回值:任意类型

原理:

  • 线程函数的返回类型是 void*,可以返回任意指针。
  • 返回值对象也最好在堆上分配,以保证主线程拿到时对象依然有效。
  • 主线程通过 pthread_join 获取返回值,并转换回原始类型使用。

代码解析:

int ret = t->add();             // 执行任务
Result* r = new Result(ret);    // 封装返回值
return (void *)r;               // 返回给主线程
  • 主线程获取返回值:
Result* ret = nullptr;
pthread_join(tid, (void **)&ret);   // 获取线程返回值
std::cout << "线程返回值:" << ret->getRet() << std::endl;
  • 使用完返回值后记得 delete 释放内存。

5. 线程终止

1)通过线程入口函数 return 终止

  • 每个线程都有自己的入口函数(start_routine)。
  • 当线程函数执行 return 时,线程就会终止。
  • 注意:不能在子线程中调用 exit() 来终止线程,因为 exit()终止整个进程,会导致主线程也退出。

示例:

void* thread_func(void* arg) {
    printf("线程开始执行\n");
    return (void*)123; // 返回值可被 pthread_join 获取
}

int main() {
    pthread_t tid;
    void* ret;
    pthread_create(&tid, NULL, thread_func, NULL);
    pthread_join(tid, &ret);
    printf("线程返回值: %ld\n", (long)ret);
}

运行结果:

线程开始执行
线程返回值: 123

2)使用 pthread_exit() 终止线程

  • pthread_exit(void *value_ptr) 可以显式地终止当前线程。
  • value_ptr 是线程退出时返回给 pthread_join 的指针,可以传递结果。
  • return 不同的是,pthread_exit 可以在任何地方调用(不仅限于函数末尾),线程立即终止,但进程继续运行。

示例:

void* thread_func(void* arg) {
    int* ret = (int*)malloc(sizeof(int));
    *ret = 456;
    pthread_exit((void*)ret); // 终止线程并返回值
}

int main() {
    pthread_t tid;
    void* ret;
    pthread_create(&tid, NULL, thread_func, NULL);
    pthread_join(tid, &ret);
    printf("线程返回值: %d\n", *(int*)ret);
    free(ret);
}

运行结果:

线程返回值: 456

⚠️ 注意:传递给 pthread_exit 的指针不能是局部变量,因为线程终止后栈会被释放。


3)线程被取消 (pthread_cancel)

  • 一个线程可以被 其他线程取消,使用 pthread_cancel(thread_id)
  • 被取消的线程退出时,pthread_join 返回的值是一个特殊常量 PTHREAD_CANCELED
  • 线程在被取消时,不会返回自己正常的返回值,而是标识被取消。

示例:

void* thread_func(void* arg) {
    while (1) {
        printf("线程正在运行...\n");
        sleep(1);
    }
    return NULL;
}

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

    pthread_create(&tid, NULL, thread_func, NULL);
    sleep(3);                 // 等待线程执行
    pthread_cancel(tid);       // 取消线程
    pthread_join(tid, &ret);

    if (ret == PTHREAD_CANCELED)
        printf("线程被取消,返回 PTHREAD_CANCELED\n");
    else
        printf("线程正常结束\n");
}

运行结果:

线程正在运行...
线程正在运行...
线程正在运行...
线程被取消,返回 PTHREAD_CANCELED

🔑 总结

  1. return:线程函数结束,线程终止,可返回结果。
  2. pthread_exit():线程立即终止,返回值可传给 pthread_join,不会影响进程其他线程。
  3. pthread_cancel():由其他线程取消线程,pthread_join 返回 PTHREAD_CANCELED,线程未完成正常逻辑。
  4. 禁止使用 exit() 终止线程exit() 会结束整个进程,包括主线程。

6. 线程等待

线程等待主要使用 pthread_join

int pthread_join(pthread_t thread, void **value_ptr);
  • thread:等待的线程 ID。
  • value_ptr:线程退出时返回的指针。
  • 如果线程通过 returnpthread_exit 终止,value_ptr 保存线程返回值。
  • 如果线程被 pthread_cancel,返回 PTHREAD_CANCELED
  • 如果不关心返回值,可传 NULL

等待线程结束是必须的,否则线程退出的资源无法释放,造成系统泄漏。

示例(同时创建多个线程,并且一个一个等待)

void *routine(void *buf)
{
    std::string str = static_cast<char *>(buf);

    int cnt = 5;
    while (cnt--)
    {
        std::cout << "我是子线程:" << str << std::endl;
    }
    return (void *)100;
}

int main()
{
    std::vector<pthread_t> tids;

    for (int i = 0; i < 5; i++)
    {
        pthread_t tid;

        char *name = new char[64];
        snprintf(name, 64, "thread-%d", i + 1);
        int n = pthread_create(&tid, nullptr, routine, (void *)name);
        if (n != 0)
        {
            std::cout << "pthread_create error: " << n << std::endl;
            return 1;
        }
        tids.push_back(tid);
    }

    for (int i = 0; i < 5; i++)
    {

        int n = pthread_join(tids[i], nullptr);
        if (n == 0)
        {
            std::cout << "线程等待成功" << std::endl;
        }
        else
        {
            std::cout << "线程等待失败" << std::endl;
        }
    }

    return 0;
}

在这里插入图片描述


7. 分离线程

默认线程是 joinable 的,需要 pthread_join 回收资源。如果不关心返回值,可以分离线程:

pthread_detach(pthread_self());

分离后线程退出会自动释放资源,不能再用 pthread_join。注意 joinable 与 detached 是互斥的。

示例:

void* thread_run(void* arg) {
    pthread_detach(pthread_self());
    printf("%s\n", (char*)arg);
    return NULL;
}

int main() {
    pthread_t tid;
    if (pthread_create(&tid, NULL, thread_run, "thread run...") != 0) {
        printf("create thread error\n");
        return 1;
    }
    sleep(1); // 给线程执行时间
}

8. 线程 ID 与进程地址空间

  • pthread_create 会生成一个线程 ID,存放在 pthread_t 类型变量中。
  • 这个 ID 是 NPTL 线程库内部维护的虚拟地址
  • 线程在内存中是轻量级进程,主线程和子线程栈地址不同,但共享进程的虚拟空间。
  • pthread_self 返回当前线程的 ID,用于线程间操作。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述


9. 总结与注意事项

  1. 线程参数:传递给线程函数的参数必须是堆上分配或全局变量,不能是局部变量。
  2. 线程返回值:通过 pthread_exitreturn 返回,内存要在堆上或全局。
  3. 线程等待:默认 joinable,需要 pthread_join 回收资源。
  4. 线程分离:如果不关心返回值,可用 pthread_detach 自动释放资源。
  5. 错误处理:pthread 函数出错通过返回值,不会设置全局 errno
  6. 线程 IDpthread_t 是库内部标识,进程内唯一,但不等同于内核线程 ID。


网站公告

今日签到

点亮在社区的每一天
去签到