在前端开发内容学习中,c语言多线程并发例子:pthread创建与互斥锁示范是常见主题。很多人在阅读时会遇到概念分散、步骤不清和注意点难以归纳的问题。本文按照基础概念、操作流程和关键细节,对相关内容进行整理。

想看懂c语言多线程并发例子,关键不在语法堆砌,而在把线程创建、共享数据、互斥保护和结果验证连成一条线。本文用一个可编译的小示例说明基本写法,并补上常见错误与排查思路。
多线程示例最适合用来说明一份共享数据如何被多个执行流同时访问。只要两个线程都去修改同一个计数器,就能很直观看到并发带来的结果差异。
学习这类例子时,建议先盯住四个动作:创建线程、传入参数、保护共享变量、等待线程结束。把这四步看清楚,比单纯背函数名更容易真正理解执行过程。
这类示例更适合在 Linux、macOS 或 WSL 中练习,因为这些环境通常直接提供 pthread 支持。编译器建议使用 gcc 或 clang,并确认系统头文件里可以正常包含 pthread.h。
如果你在 Windows 原生环境中操作,默认命令行和编译环境往往不能直接按本文命令运行。更稳妥的做法是使用 WSL、MinGW-w64,或其他已经确认支持 pthread 的工具链,再执行后面的编译与运行命令。
下面先给出未加锁版本,再给出加锁版本。这样你能直接看到“并发问题本身”和“互斥锁修复后的结果”有什么区别。
本例固定使用 2 个线程、每个线程循环 100000 次,所以理论结果应为 200000。未加锁时,最终值可能小于 200000;加锁后,如果线程创建、加锁和回收都成功,结果应稳定为 200000。
未加锁版本:演示竞争条件
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#define LOOP_COUNT 100000
int counter = 0;
void *worker(void *arg) {
int i;
const char *name = (const char *)arg;
for (i = 0; i < LOOP_COUNT; i++) {
counter++;
}
printf("%s finishedn", name);
return NULL;
}
int main(void) {
pthread_t t1, t2;
int rc;
rc = pthread_create(&t1, NULL, worker, "thread-1");
if (rc != 0) {
fprintf(stderr, "pthread_create t1 failed: %dn", rc);
return 1;
}
rc = pthread_create(&t2, NULL, worker, "thread-2");
if (rc != 0) {
fprintf(stderr, "pthread_create t2 failed: %dn", rc);
pthread_join(t1, NULL);
return 1;
}
rc = pthread_join(t1, NULL);
if (rc != 0) {
fprintf(stderr, "pthread_join t1 failed: %dn", rc);
return 1;
}
rc = pthread_join(t2, NULL);
if (rc != 0) {
fprintf(stderr, "pthread_join t2 failed: %dn", rc);
return 1;
}
printf("final counter = %d (expected 200000)n", counter);
return 0;
}加锁版本:用互斥锁修复
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#define LOOP_COUNT 100000
int counter = 0;
pthread_mutex_t lock;
void *worker(void *arg) {
int i;
int rc;
const char *name = (const char *)arg;
for (i = 0; i < LOOP_COUNT; i++) {
rc = pthread_mutex_lock(&lock);
if (rc != 0) {
fprintf(stderr, "pthread_mutex_lock failed: %dn", rc);
return NULL;
}
counter++;
rc = pthread_mutex_unlock(&lock);
if (rc != 0) {
fprintf(stderr, "pthread_mutex_unlock failed: %dn", rc);
return NULL;
}
}
printf("%s finishedn", name);
return NULL;
}
int main(void) {
pthread_t t1, t2;
int rc;
rc = pthread_mutex_init(&lock, NULL);
if (rc != 0) {
fprintf(stderr, "pthread_mutex_init failed: %dn", rc);
return 1;
}
rc = pthread_create(&t1, NULL, worker, "thread-1");
if (rc != 0) {
fprintf(stderr, "pthread_create t1 failed: %dn", rc);
pthread_mutex_destroy(&lock);
return 1;
}
rc = pthread_create(&t2, NULL, worker, "thread-2");
if (rc != 0) {
fprintf(stderr, "pthread_create t2 failed: %dn", rc);
pthread_join(t1, NULL);
pthread_mutex_destroy(&lock);
return 1;
}
rc = pthread_join(t1, NULL);
if (rc != 0) {
fprintf(stderr, "pthread_join t1 failed: %dn", rc);
pthread_mutex_destroy(&lock);
return 1;
}
rc = pthread_join(t2, NULL);
if (rc != 0) {
fprintf(stderr, "pthread_join t2 failed: %dn", rc);
pthread_mutex_destroy(&lock);
return 1;
}
printf("final counter = %d (expected 200000)n", counter);
rc = pthread_mutex_destroy(&lock);
if (rc != 0) {
fprintf(stderr, "pthread_mutex_destroy failed: %dn", rc);
return 1;
}
return 0;
}cc -std=c11 -Wall -Wextra demo.c -o demo -pthread./demopthread_create用于启动线程,参数里最需要关注的是线程函数和传入数据。线程函数的返回类型要与pthread接口要求保持一致,这样线程创建和回收过程才不会出错。
真正体现并发风险的是共享计数器这类变量。未加锁版本中的 counter++ 看起来只有一行,但它至少包含读取、计算和写回几个步骤。两个线程同时执行时,这几个步骤可能互相交错,最后导致结果小于理论值 200000,这种现象就是典型的竞争条件。
加锁版本把 counter++ 放进 pthread_mutex_lock 和 pthread_mutex_unlock 之间,相当于把这段临界区串行化处理。pthread_join 的作用则是保证主线程在输出最终结果前,两个工作线程都已经执行完毕,否则你看到的结果可能还没累加完整。
很多初学者照着写完代码后,程序能编译却看不出并发效果,通常是因为没有共享变量、没有循环放大竞争,或者线程刚启动主线程就结束了。这些都会让示例失去教学价值。
如果输出结果异常,先检查三处:互斥锁是否成功初始化,线程函数是否真的执行了足够次数,线程是否都被正确回收。本文示例里对 pthread_mutex_init、pthread_create、pthread_join、pthread_mutex_lock 和 pthread_mutex_unlock 都做了返回值检查,就是为了把“能运行”和“能排错”区分开。
实际处理时可以这样理解:初始化失败时,说明锁资源还没准备好,程序应直接退出;线程创建失败时,不要继续假设两个线程都在工作,而应先回收已成功启动的线程,再结束程序。这样写不仅是演示互斥锁,也更接近真实项目里的排查思路。
看c语言多线程并发例子,重点不是把接口全部记住,而是看清共享数据怎样被安全地访问。先用未加锁版本认识竞争条件,再用互斥锁把结果稳定到理论值 200000,最后把创建、同步、回收和错误处理这条主线练熟,后面扩展到更复杂的并发结构会更稳。