位置:首页 > C > c语言多线程编程|pthread创建同步与回收示例

c语言多线程编程|pthread创建同步与回收示例

时间:2026-08-10  |  作者:半糖攻略君  |  阅读:0

C语言多线程编程在很多场景下实际是指基于POSIX Threads(pthread)的并发编程方式,主要适用于Linux、Unix及其他支持pthreads的环境。本文围绕pthread_create、参数传递、线程同步、资源回收与常见问题展开,给出可编译运行的示例代码,帮助读者把线程创建、共享数据保护、失败路径处理和调试排查真正落到代码层面;如果是在Windows原生环境中开发,通常需要改用Windows线程API或使用相应的pthread兼容库。

理解线程与适用场景

线程是进程中的执行单元,同一进程内的线程共享代码区、全局变量和打开的文件等资源,但各自拥有独立的栈和执行状态。多线程适合处理相互独立且可以并发推进的任务,例如网络连接处理、后台计算、文件读写和生产者消费者模型。线程并不一定让程序运行得更快,若任务规模较小、共享数据过多或同步频繁,线程管理成本反而可能降低效率。开始设计前,应先明确哪些任务可以并行、哪些数据需要共享,以及程序如何结束所有线程。本文示例使用POSIX线程库pthread,主要面向Linux、Unix及其他兼容环境;若读者使用的是Windows原生线程模型,需要改用CreateThread等API,或使用兼容层后再参考相同思路。

创建线程并传递参数

在支持POSIX线程的环境中,可通过pthread_create创建线程。调用时需要提供线程标识、属性、入口函数和入口参数。入口函数通常接收一个void指针并返回一个void指针,因此可以借助结构体一次传递多个参数。传入的数据必须在线程使用期间保持有效,不能把即将离开作用域的局部变量地址交给仍在运行的线程。pthread系列函数大多直接返回错误码而不是设置errno,因此处理失败时应保存返回值,再结合strerror打印错误信息。下面给出一个完整示例,演示结构体参数传递、返回值检查、只回收已成功创建的线程,以及统一清理逻辑。

示例代码:

#include 
#include 
#include 
#include 

typedef struct {
    int index;
    const char *message;
} ThreadArg;

static void *worker(void *arg) {
    ThreadArg *data = (ThreadArg *)arg;
    printf("thread %d: %sn", data->index, data->message);
    return NULL;
}

int main(void) {
    enum { THREAD_COUNT = 3 };
    pthread_t threads[THREAD_COUNT];
    ThreadArg *args = calloc(THREAD_COUNT, sizeof(ThreadArg));
    int created = 0;
    int rc = 0;

    if (args == NULL) {
        perror("calloc");
        return 1;
    }

    for (int i = 0; i < THREAD_COUNT; ++i) {
        args[i].index = i;
        args[i].message = "hello from pthread";

        rc = pthread_create(&threads[i], NULL, worker, &args[i]);
        if (rc != 0) {
            fprintf(stderr, "pthread_create failed at %d: %sn", i, strerror(rc));
            break;
        }
        created++;
    }

    for (int i = 0; i < created; ++i) {
        rc = pthread_join(threads[i], NULL);
        if (rc != 0) {
            fprintf(stderr, "pthread_join failed at %d: %sn", i, strerror(rc));
        }
    }

    free(args);
    return created == THREAD_COUNT  0 : 1;
}

编译命令:

gcc demo_create.c -pthread -o demo_create

可能输出:

thread 0: hello from pthread
thread 1: hello from pthread
thread 2: hello from pthread

这个示例的重点是:创建线程失败后不再继续启动新线程,只对已经创建成功的线程执行pthread_join,再统一释放动态分配的参数内存。这样即使中途失败,也不会错误地回收未创建的线程。

  • 检查pthread_createpthread_join的返回值,pthread函数失败时应根据返回码配合strerror输出错误信息
  • 为每个线程准备独立且生命周期足够长的参数数据,避免传入即将失效的局部变量地址
  • 线程部分创建成功时,记录成功数量并只回收已创建线程,避免错误地join无效线程

保护共享数据

多个线程同时读写同一数据时,可能发生数据竞争,导致结果不稳定或内存状态损坏。互斥锁可以保证同一时刻只有一个线程进入临界区,条件变量适合在线程之间等待某个状态成立。实际编码时,至少要明确初始化、加锁、等待、通知、解锁和销毁这些步骤,并且让等待线程始终在循环中检查条件,避免虚假唤醒。下面给出一个最小同步示例:两个工作线程累加共享计数,主线程等待计数达到目标值。

示例代码:

#include 
#include 
#include 
#include 

typedef struct {
    pthread_mutex_t mutex;
    pthread_cond_t cond;
    int counter;
    int target;
} SharedState;

static void cleanup_state(SharedState *state) {
    pthread_cond_destroy(&state->cond);
    pthread_mutex_destroy(&state->mutex);
}

static void *counter_worker(void *arg) {
    SharedState *state = (SharedState *)arg;

    for (int i = 0; i < 50000; ++i) {
        pthread_mutex_lock(&state->mutex);
        state->counter++;
        if (state->counter >= state->target) {
            pthread_cond_signal(&state->cond);
        }
        pthread_mutex_unlock(&state->mutex);
    }

    return NULL;
}

int main(void) {
    SharedState state;
    pthread_t t1, t2;
    int rc;

    state.counter = 0;
    state.target = 100000;

    rc = pthread_mutex_init(&state.mutex, NULL);
    if (rc != 0) {
        fprintf(stderr, "pthread_mutex_init failed: %sn", strerror(rc));
        return 1;
    }

    rc = pthread_cond_init(&state.cond, NULL);
    if (rc != 0) {
        fprintf(stderr, "pthread_cond_init failed: %sn", strerror(rc));
        pthread_mutex_destroy(&state.mutex);
        return 1;
    }

    rc = pthread_create(&t1, NULL, counter_worker, &state);
    if (rc != 0) {
        fprintf(stderr, "pthread_create t1 failed: %sn", strerror(rc));
        cleanup_state(&state);
        return 1;
    }

    rc = pthread_create(&t2, NULL, counter_worker, &state);
    if (rc != 0) {
        fprintf(stderr, "pthread_create t2 failed: %sn", strerror(rc));
        pthread_join(t1, NULL);
        cleanup_state(&state);
        return 1;
    }

    pthread_mutex_lock(&state.mutex);
    while (state.counter < state.target) {
        pthread_cond_wait(&state.cond, &state.mutex);
    }
    printf("counter reached %dn", state.counter);
    pthread_mutex_unlock(&state.mutex);

    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    cleanup_state(&state);
    return 0;
}

编译命令:

gcc demo_sync.c -pthread -o demo_sync

这段代码覆盖了pthread_mutex_initpthread_mutex_lockpthread_mutex_unlockpthread_cond_initpthread_cond_waitpthread_cond_signal和销毁流程。临界区内只执行共享计数修改与条件通知,避免把无关耗时操作放进锁内;一旦需要多个锁,还应规定统一的加锁顺序,以减少死锁风险。

  • 共享状态的读取和修改应采用一致的同步策略,不能一部分加锁一部分裸读写
  • 等待条件变量时使用while循环反复检查状态,避免虚假唤醒造成逻辑错误
  • 互斥锁、条件变量在不再使用后应调用pthread_mutex_destroypthread_cond_destroy完成清理

线程结束、失败路径与错误排查

线程入口函数返回或调用pthread_exit后,该线程结束,但可连接线程仍需由其他线程执行pthread_join回收。若线程无需返回结果,可将其设置为分离状态,使系统在线程退出后自动释放资源。资源回收不只包括线程本身,也包括动态参数、互斥锁、条件变量等辅助对象。实际开发时,建议采用统一清理逻辑:记录已成功创建的线程数量,任何一步失败都跳转到同一清理出口,只回收已创建的线程,再释放锁和动态内存。若线程函数内部持锁后可能提前返回,应保证所有分支都能正确解锁,否则极易留下死锁。

并发问题排查可以直接借助ThreadSanitizer。它适合发现未加锁共享读写、错误的同步假设和部分使用后失效问题。常见做法是:

gcc race_demo.c -pthread -fsanitize=thread -g -o race_demo
./race_demo

如果程序存在数据竞争,ThreadSanitizer通常会报告两个线程在同一内存地址上的冲突访问,并标出读写发生的函数、代码位置和线程创建栈。排查时可重点看三类信息:哪一个共享变量发生冲突、冲突访问是否缺少同一把锁保护、线程是从哪里创建出来的。若报告显示一个线程写入共享变量,另一个线程同时读取同一地址,而两边都没有经过同一同步原语,这通常就说明需要补充互斥锁、原子操作或条件同步。

  • 避免主线程过早返回并终止整个进程,必要时等待所有可连接线程结束
  • 不要对同一线程重复执行pthread_join,失败路径中也应只回收已成功创建的线程
  • 使用-fsanitize=thread配合压力测试运行程序,优先定位数据竞争、锁使用不一致和线程退出顺序问题

可靠的pthread多线程程序依赖清楚的任务划分、严格的共享数据管理和完整的资源回收。先从少量线程和简单同步关系开始,逐步补齐返回值检查、失败路径清理和检测工具验证,再扩展到更复杂的并发模型,通常更容易控制复杂度并定位潜在问题。

免责声明:文中图文均来自网络,如有侵权请联系删除,心愿游戏发布此文仅为传递信息,不代表心愿游戏认同其观点或证实其描述。

相关文章

更多

精选合集

更多

大家都在玩

热门话题

大家都在看

更多