位置:首页 > C > c多线程编程实例 pthread创建同步与编译运行

c多线程编程实例 pthread创建同步与编译运行

时间:2026-08-10  |  作者:清风无痕  |  阅读:0

本文通过一个基于pthread的完整C语言实例,说明线程创建、参数传递、等待回收和互斥锁同步的基本方法,并分析共享数据竞争等常见问题,帮助读者掌握多线程程序从编写、编译到安全运行的完整过程。

实例目标与运行环境

下面以POSIX线程库为基础设计一个计数实例。主线程创建两个工作线程,每个线程循环增加同一个共享计数器。由于自增操作并非不可分割,两个线程同时读写计数器时可能覆盖彼此的结果,因此需要使用互斥锁保护临界区。该实例适用于支持pthread的Linux及其他类Unix环境。

  • 引入pthread.h头文件,使用pthread_t保存线程标识。
  • 使用共享计数器演示多个线程访问同一数据的场景。
  • 使用pthread_mutex_t互斥锁保证每次自增操作完整执行。

创建线程与传递参数

线程入口函数通常接收一个void指针并返回一个void指针。可以定义参数结构体保存线程编号和循环次数,再把对应结构体的地址传给pthread_create。参数对象在线程结束前必须保持有效,不能传入已经离开作用域的局部对象地址。创建成功时pthread_create返回零,失败时应保存返回码并输出错误信息,同时避免等待未创建的线程。

  • 分别准备两个参数结构体,避免工作线程错误地共用同一份可变参数。
  • 调用pthread_create时传入线程标识地址、属性、入口函数和参数地址。
  • 在线程函数中先把void指针转换为正确的结构体指针,再读取参数。

完整可编译的计数实例

下面的source.c包含参数结构体、线程入口函数、共享计数器、互斥锁、两个线程的创建状态、条件化pthread_join以及互斥锁销毁。两个线程各执行100000次加一操作。即使某次pthread_create失败,程序也只会等待实际创建成功的线程,并通过strerror把pthread返回码转换为明确的错误信息。

#include 
#include 
#include 
#include 
#include 

typedef struct {
    int thread_id;
    long iterations;
} ThreadArgs;

static long shared_counter = 0;
static pthread_mutex_t counter_mutex = PTHREAD_MUTEX_INITIALIZER;

static void *count_worker(void *arg)
{
    const ThreadArgs *args = (const ThreadArgs *)arg;

    for (long i = 0; i < args->iterations; ++i) {
        int rc = pthread_mutex_lock(&counter_mutex);
        if (rc != 0) {
            fprintf(stderr, "thread %d: pthread_mutex_lock: %sn",
                    args->thread_id, strerror(rc));
            return (void *)(long)rc;
        }

        ++shared_counter;

        rc = pthread_mutex_unlock(&counter_mutex);
        if (rc != 0) {
            fprintf(stderr, "thread %d: pthread_mutex_unlock: %sn",
                    args->thread_id, strerror(rc));
            return (void *)(long)rc;
        }
    }

    return NULL;
}

int main(void)
{
    pthread_t threads[2];
    ThreadArgs args[2] = {
        { .thread_id = 1, .iterations = 100000 },
        { .thread_id = 2, .iterations = 100000 }
    };
    int create_rc[2];
    int program_failed = 0;

    for (int i = 0; i < 2; ++i) {
        create_rc[i] = pthread_create(&threads[i], NULL, count_worker, &args[i]);
        if (create_rc[i] != 0) {
            fprintf(stderr, "pthread_create for thread %d: %sn",
                    args[i].thread_id, strerror(create_rc[i]));
            program_failed = 1;
        }
    }

    for (int i = 0; i < 2; ++i) {
        if (create_rc[i] == 0) {
            void *thread_result = NULL;
            int join_rc = pthread_join(threads[i], &thread_result);
            if (join_rc != 0) {
                fprintf(stderr, "pthread_join for thread %d: %sn",
                        args[i].thread_id, strerror(join_rc));
                program_failed = 1;
            } else if (thread_result != NULL) {
                fprintf(stderr, "thread %d exited with error code %ldn",
                        args[i].thread_id, (long)thread_result);
                program_failed = 1;
            }
        }
    }

    printf("final counter: %ldn", shared_counter);

    int destroy_rc = pthread_mutex_destroy(&counter_mutex);
    if (destroy_rc != 0) {
        fprintf(stderr, "pthread_mutex_destroy: %sn", strerror(destroy_rc));
        program_failed = 1;
    }

    return program_failed  EXIT_FAILURE : EXIT_SUCCESS;
}
  • create_rc数组分别保存两次pthread_create的返回值,只有返回值为0时才执行对应的pthread_join
  • pthread_createpthread_join和互斥锁操作的错误码均通过strerror转换为可读信息。
  • 所有已创建线程完成后才销毁互斥锁,参数数组在main结束前始终有效。

使用互斥锁保护共享计数器

每次修改共享计数器前调用pthread_mutex_lock,修改完成后调用pthread_mutex_unlock。锁定范围应只覆盖必须互斥的代码,耗时计算、文件读取或休眠等操作不宜放在临界区内,否则会降低并发效率。互斥锁可以使用PTHREAD_MUTEX_INITIALIZER静态初始化,也可以通过pthread_mutex_init动态初始化。无论采用哪种初始化方式,只要互斥锁仍处于有效状态且后续不再使用,都应在没有线程持有或等待该锁时调用pthread_mutex_destroy

  • 获得互斥锁后执行共享计数器自增。
  • 离开临界区前必须释放互斥锁,所有分支都应遵守这一规则。
  • 确认所有相关线程已经退出且无人持有或等待互斥锁后,再调用pthread_mutex_destroy

编译运行并检查结果

把完整代码保存为source.c,然后使用gcc source.c -o demo -pthread编译,运行命令为./demo。两个线程都成功创建并各完成十万次自增时,典型输出为final counter: 200000。程序也会检查pthread_join的返回值;如果线程创建、执行、等待或互斥锁销毁失败,将向标准错误输出具体原因并以失败状态退出。

  • 编译命令:gcc source.c -o demo -pthread
  • 运行命令:./demo
  • 典型输出:final counter: 200000

常见错误与改进方向

多线程程序的问题往往具有偶发性,一次运行正确并不能证明没有数据竞争。可以暂时去掉count_worker中的pthread_mutex_lockpthread_mutex_unlock进行对照测试:shared_counter的自增会变成未同步的并发读写,程序存在数据竞争,结果属于未定义行为,常见现象是最终值小于200000,也可能偶尔看似正确。不要把某一次输出当作无锁写法可靠的证据。忘记加锁、提前释放参数、多个线程写入同一缓冲区以及线程未回收,也会造成错误或资源泄漏。实际项目还应根据任务模型选择条件变量、读写锁或线程池,并明确线程之间的数据所有权和退出流程。

  • 不要依赖运行速度或休眠时间维持线程执行顺序。
  • 不要在持有互斥锁时执行无法确定耗时的操作。
  • 共享状态较多时,应封装访问函数并统一管理加锁和解锁。

完成线程创建、参数传递、错误处理、同步和回收后,一个基础的C多线程程序就具备了可编译、可验证的可靠结构。继续扩展任务队列和条件变量,可以逐步实现更实用的并发处理程序。

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

相关文章

更多

精选合集

更多

大家都在玩

热门话题

大家都在看

更多