C语言多线程同步详解:从互斥锁到条件变量

news/2025/9/24 16:31:57/文章来源:https://www.cnblogs.com/mogaoyizhang/p/19109483

在多线程编程中,线程同步是确保多个线程正确协作的关键技术。当多个线程访问共享资源时,如果没有适当的同步机制,可能会导致数据竞争、死锁等问题。本文将详细介绍C语言中常用的线程同步技术。

为什么需要线程同步?

想象一下银行账户操作的经典例子:

  • 线程A:读取余额(100元) → 存入50元 → 写入新余额(150元)
  • 线程B:读取余额(100元) → 取出30元 → 写入新余额(70元)

如果没有同步,最终余额可能是70元(线程B覆盖了线程A的修改),而不是正确的120元。这就是典型的数据竞争问题。

基本的同步机制

1. 互斥锁 (Mutex)

互斥锁是最基本的同步机制,用于保护临界区,确保同一时间只有一个线程可以访问共享资源。

#include <pthread.h>
#include <stdio.h>
#include <unistd.h>pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
int shared_counter = 0;void* increment_thread(void* arg) {for (int i = 0; i < 5; i++) {pthread_mutex_lock(&mutex);  // 加锁// 临界区开始int temp = shared_counter;printf("Thread %ld: read value = %d\n", (long)arg, temp);usleep(1000);  // 模拟一些处理时间shared_counter = temp + 1;printf("Thread %ld: updated value = %d\n", (long)arg, shared_counter);// 临界区结束pthread_mutex_unlock(&mutex);  // 解锁usleep(10000);  // 让出CPU给其他线程}return NULL;
}int main() {pthread_t t1, t2, t3;// 创建三个线程pthread_create(&t1, NULL, increment_thread, (void*)1);pthread_create(&t2, NULL, increment_thread, (void*)2);pthread_create(&t3, NULL, increment_thread, (void*)3);// 等待所有线程完成pthread_join(t1, NULL);pthread_join(t2, NULL);pthread_join(t3, NULL);printf("Final counter value: %d (expected: 15)\n", shared_counter);pthread_mutex_destroy(&mutex);  // 销毁互斥锁return 0;
}

2. 条件变量 (Condition Variables)

条件变量用于线程间的通信,允许线程等待特定条件发生。

#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>#define BUFFER_SIZE 5pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond_producer = PTHREAD_COND_INITIALIZER;
pthread_cond_t cond_consumer = PTHREAD_COND_INITIALIZER;int buffer[BUFFER_SIZE];
int count = 0;  // 缓冲区中元素数量
int in = 0;     // 生产者插入位置
int out = 0;    // 消费者取出位置void* producer(void* arg) {int item;for (int i = 0; i < 10; i++) {item = rand() % 100;  // 生产一个随机数pthread_mutex_lock(&mutex);// 如果缓冲区满,等待消费者消费while (count == BUFFER_SIZE) {printf("Producer: buffer full, waiting...\n");pthread_cond_wait(&cond_producer, &mutex);}// 生产物品buffer[in] = item;in = (in + 1) % BUFFER_SIZE;count++;printf("Producer: produced item %d, count = %d\n", item, count);pthread_cond_signal(&cond_consumer);  // 通知消费者pthread_mutex_unlock(&mutex);usleep(rand() % 100000);}return NULL;
}void* consumer(void* arg) {int item;for (int i = 0; i < 10; i++) {pthread_mutex_lock(&mutex);// 如果缓冲区空,等待生产者生产while (count == 0) {printf("Consumer: buffer empty, waiting...\n");pthread_cond_wait(&cond_consumer, &mutex);}// 消费物品item = buffer[out];out = (out + 1) % BUFFER_SIZE;count--;printf("Consumer: consumed item %d, count = %d\n", item, count);pthread_cond_signal(&cond_producer);  // 通知生产者pthread_mutex_unlock(&mutex);usleep(rand() % 100000);}return NULL;
}int main() {pthread_t prod, cons;srand(time(NULL));pthread_create(&prod, NULL, producer, NULL);pthread_create(&cons, NULL, consumer, NULL);pthread_join(prod, NULL);pthread_join(cons, NULL);pthread_mutex_destroy(&mutex);pthread_cond_destroy(&cond_producer);pthread_cond_destroy(&cond_consumer);return 0;
}

3. 读写锁 (Read-Write Lock)

读写锁允许多个读操作同时进行,但写操作需要独占访问。

#include <pthread.h>
#include <stdio.h>
#include <unistd.h>pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;
int shared_data = 0;
int readers_count = 0;void* reader(void* arg) {int id = (long)arg;for (int i = 0; i < 5; i++) {pthread_rwlock_rdlock(&rwlock);  // 获取读锁readers_count++;printf("Reader %d: data = %d (total readers: %d)\n", id, shared_data, readers_count);usleep(50000);  // 模拟读取时间readers_count--;pthread_rwlock_unlock(&rwlock);usleep(100000);}return NULL;
}void* writer(void* arg) {int id = (long)arg;for (int i = 0; i < 3; i++) {pthread_rwlock_wrlock(&rwlock);  // 获取写锁shared_data++;printf("Writer %d: updated data to %d\n", id, shared_data);usleep(100000);  // 模拟写入时间pthread_rwlock_unlock(&rwlock);usleep(200000);}return NULL;
}int main() {pthread_t readers[3], writers[2];// 创建读者线程for (long i = 0; i < 3; i++) {pthread_create(&readers[i], NULL, reader, (void*)(i + 1));}// 创建写者线程for (long i = 0; i < 2; i++) {pthread_create(&writers[i], NULL, writer, (void*)(i + 1));}// 等待所有线程完成for (int i = 0; i < 3; i++) {pthread_join(readers[i], NULL);}for (int i = 0; i < 2; i++) {pthread_join(writers[i], NULL);}pthread_rwlock_destroy(&rwlock);return 0;
}

4. 信号量 (Semaphore)

信号量用于控制对有限资源的访问。

#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <semaphore.h>#define NUM_CHAIRS 3sem_t barber_ready;    // 理发师是否准备好
sem_t customer_ready;  // 顾客是否准备好
sem_t access_chairs;   // 保护椅子访问的互斥信号量int waiting_customers = 0;void* barber(void* arg) {while (1) {printf("Barber: sleeping...\n");sem_wait(&customer_ready);  // 等待顾客sem_wait(&access_chairs);waiting_customers--;sem_post(&barber_ready);  // 通知理发师准备好了sem_post(&access_chairs);printf("Barber: cutting hair...\n");usleep(200000);  // 理发时间printf("Barber: finished cutting hair\n");}return NULL;
}void* customer(void* arg) {int id = (long)arg;sem_wait(&access_chairs);if (waiting_customers < NUM_CHAIRS) {waiting_customers++;printf("Customer %d: took a seat, waiting customers: %d\n", id, waiting_customers);sem_post(&customer_ready);  // 通知理发师有顾客sem_post(&access_chairs);sem_wait(&barber_ready);  // 等待理发师printf("Customer %d: getting haircut\n", id);} else {sem_post(&access_chairs);printf("Customer %d: no seats available, leaving\n", id);}return NULL;
}int main() {pthread_t barber_thread, customer_threads[10];// 初始化信号量sem_init(&barber_ready, 0, 0);sem_init(&customer_ready, 0, 0);sem_init(&access_chairs, 0, 1);pthread_create(&barber_thread, NULL, barber, NULL);// 创建顾客线程for (long i = 0; i < 10; i++) {pthread_create(&customer_threads[i], NULL, customer, (void*)(i + 1));usleep(100000);  // 顾客陆续到达}// 等待所有顾客完成for (int i = 0; i < 10; i++) {pthread_join(customer_threads[i], NULL);}// 清理资源sem_destroy(&barber_ready);sem_destroy(&customer_ready);sem_destroy(&access_chairs);return 0;
}

同步机制对比

机制 适用场景 优点 缺点
互斥锁 保护临界区,防止数据竞争 简单易用,性能较好 可能引起死锁
条件变量 线程间通信,等待特定条件 高效的事件通知机制 必须与互斥锁配合使用
读写锁 读多写少的场景 允许多个读操作并发 写操作可能饿死
信号量 资源池管理,生产者消费者 灵活的计数机制 使用相对复杂

避免常见陷阱

1. 死锁预防

// 错误的做法:可能导致死锁
void transfer_wrong(account_t* a, account_t* b, int amount) {pthread_mutex_lock(&a->mutex);pthread_mutex_lock(&b->mutex);// 转账操作...pthread_mutex_unlock(&b->mutex);pthread_mutex_unlock(&a->mutex);
}// 正确的做法:按固定顺序加锁
void transfer_correct(account_t* a, account_t* b, int amount) {// 按地址顺序加锁,避免死锁if (a < b) {pthread_mutex_lock(&a->mutex);pthread_mutex_lock(&b->mutex);} else {pthread_mutex_lock(&b->mutex);pthread_mutex_lock(&a->mutex);}// 转账操作...pthread_mutex_unlock(&b->mutex);pthread_mutex_unlock(&a->mutex);
}

2. 条件变量的正确使用

// 错误的做法:可能错过信号
while (!condition) {pthread_cond_wait(&cond, &mutex);
}// 正确的做法:使用while循环检查条件
pthread_mutex_lock(&mutex);
while (!condition) {  // 必须用while,不能用ifpthread_cond_wait(&cond, &mutex);
}
// 处理条件满足的情况
pthread_mutex_unlock(&mutex);

性能优化建议

  1. 减小锁的粒度:尽量缩短持有锁的时间
  2. 使用读写锁替代互斥锁:在读多写少的场景下
  3. 避免锁的嵌套:减少死锁风险
  4. 考虑无锁数据结构:对于性能要求极高的场景

总结

C语言的多线程同步机制虽然相对底层,但提供了强大的灵活性。通过合理使用互斥锁、条件变量、读写锁和信号量,可以构建出高效、安全的并发程序。关键在于理解每种同步机制的特性和适用场景,避免常见的陷阱如死锁和竞态条件。

在实际开发中,建议先从简单的互斥锁开始,根据具体需求逐步引入更复杂的同步机制。同时,要养成良好的编程习惯,如及时释放锁、正确使用条件变量等,这样才能写出稳健的多线程程序。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/915946.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

收废铁的做网站有优点吗完整网站设计

一、卸载 1. sudo apt-get autoclean 如果你的硬盘空间不大的话&#xff0c;可以定期运行这个程序&#xff0c;将已经删除了的软件包的.deb安装文件从硬盘中删除掉。如果你仍然需要硬盘空间的话&#xff0c;可以试试apt-get clean&#xff0c;这会把你已安装的软件包的安装包也…

微网站的好处服务器架设国外做违法网站

文章目录 给飞行中的飞机换引擎安全意识十原则开发层面产品层面运维层面给飞行中的飞机换引擎 所谓给飞行中的飞机(或飞驰的汽车)换引擎,说的是我们需要对一个正在飞速发展的系统进行大幅度的架构改造,比如把 All-in-one 的架构改造成微服务架构,尽可能减少或者消除停服的…

企业网站建设前言宁海县做企业网站

数据挖掘主要侧重解决四类问题&#xff1a;分类、聚类、关联、预测。数据挖掘非常清晰的界定了它所能解决的几类问题。这是一个高度的归纳&#xff0c;数据挖掘的应用就是把这几类问题演绎的一个过程。 数据挖掘最重要的要素是分析人员的相关业务知识和思维模式。一般来说&…

确实网站的建设目标一个网站突然打不开

https://www.jb51.net/article/106525.htm 本文实例讲述了JS实现的五级联动菜单效果。分享给大家供大家参考&#xff0c;具体如下&#xff1a; js实现多级联动的方法很多&#xff0c;这里给出一种5级联动的例子&#xff0c;其实可以扩展成N级联动,在做项目的时候碰到了这样一…

Browser Use调用浏览器入门

用的是deepseek的api 一定要去官网看示例,网上的文章都比较老了,python的很多库版本基本都是不兼容的。新版的api跟老版的区别很大、、 运行的时候,要把电脑的代理关了,或者os设置一下不走代理。详情见 https://gi…

安防视频监控新时代:国标GB28181平台EasyGBS的可视化首页如何重塑运维与管理体验?

在视频监控迈入全面联网、集中管理的时代,GB/T28181国家标准已成为实现设备互联互通的核心基石。然而,仅仅实现接入是远远不够的,如何高效、直观地管理和运维海量视频资源成为新的挑战。本文将深入探讨基于GB28181协…

What is bad statistics

Bad statistics must involve self proof of the authors viewpoint and establish on a few of samples. Mathematical statistics only establishes on a huge sample space like PHYSICS. So the findings of PHYSI…

LazyForEach性能优化:解决长列表卡顿问题

本文将深入解析HarmonyOS中LazyForEach的工作原理、性能优势、实战优化技巧及常见问题解决方案,帮助你构建流畅的长列表体验。1. LazyForEach 核心优势与原理 LazyForEach 是鸿蒙ArkUI框架中为高性能列表渲染设计的核…

完整教程:SWR:React 数据获取的现代解决方案

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

Redis数据结构的最佳实践 - 公众号

本文已收录在Github,关注我,紧跟本系列专栏文章,咱们下篇再续!🚀 魔都架构师 | 全网30W技术追随者 🔧 大厂分布式系统/数据中台实战专家 🏆 主导交易系统百万级流量调优 & 车联网平台架构 🧠 AIGC应用…

PyTorch 神经网络工具箱 - 实践

PyTorch 神经网络工具箱 - 实践2025-09-24 16:21 tlnshuju 阅读(0) 评论(0) 收藏 举报pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !importa…

java函数式编程的学习01

java函数式编程:在stream流中经常用到 对stream流的理解:操作集合的一种方法 stream流的用法:创建流、中间操作、终结操作 创建流的方式以及一些注意事项: 如果是集合通过.stream()方法来创建流,如果是数组,可以…

Manim实现镜面反射特效

本文将介绍如何使用ManimCE框架实现镜面反射特效,让你的动画更加生动有趣。 1. 实现原理 1.1. 对称点计算 实现镜面反射的核心是计算点关于直线的对称点。 代码中的symmetry_point函数通过向量投影的方法计算对称点:…

25Java基础之IO(二)

IO流-字符流 FileReader(文件字符输入流)作用:以内存为基准,可以把文件中的数据以字符的形式读入到内存中去。案例:读取一个字符//目标:文件字符输入流的使用,每次读取一个字符。 public class FileReaderDemo01 …

【git】统计项目下每个人提交行数

git log --format=%aN | sort -u | while read name; do echo -en "$name\t"; git log --author="$name" --pretty=tformat: --numstat | awk { add += $1; subs += $2; loc += $1 - $2 } END { p…

【P2860】[USACO06JAN] Redundant Paths G - Harvey

题意 给定一个连通图,求最少要加多少条边使得图无割边。 思路 首先,我们可以先缩点再进行考虑。 缩点后整个连通图变成一棵树,为了使连边后不出现割边,可以将所有度为 \(1\) 的点两两连边,如果度为 \(1\) 的点的个…

GUI软件构造

GUI(桌面图形用户界面) 设计遵循规范,要标准,不繁杂 JAVA GUI设计模式 观察者模式是一种软件设计模式 ,他定义了一种一对多的依赖关系,一个对象改变其他对象自动更新 包含的角色 被观察对象(subject) 具体被观…

网站页面建设方案书模板wordpress模班之家

1. 字面含义不同 Comparable字面意思是“具有比较能力”&#xff0c;Comparator字面意思是“比较器”。 2. 用法不同 Comparable用法&#xff1a;对需要排序的类&#xff0c;实现Comparable接口&#xff0c;重写compareTo()方法。 Comparator用法&#xff1a;创建自定义比较…

ssh蒙语网站开发室内设计公司办公室图片

在孩子学习过程中&#xff0c;假设有一种“方法”&#xff0c;能让孩子成绩突飞猛进&#xff0c;你想不想掌握&#xff1f;在孩子学习过程中&#xff0c;假设有一套“系统”&#xff0c;能让孩子主动喜欢上学习&#xff0c;你想不想拥有&#xff1f;在孩子学习过程中&#xff0…

点餐网站怎么做哈尔滨网站建设制作

导读:本文主要围绕材料非线性问题的有限元Matlab编程求解进行介绍,重点围绕牛顿-拉普森法(切线刚度法)、初应力法、初应变法等三种非线性迭代方法的算法原理展开讲解,最后利用Matlab对材料非线性问题有限元迭代求解算法进行实现,展示了实现求解的核心代码。这些内容都将收…