网站首页 > 技术文章 正文
互斥锁
数据竞争
在并发编程中,数据竞争是指多个线程同时对共享数据进行读写操作,并且至少有一个线程进行写操作,从而导致未定义的行为或结果。 下面给出一个例子:
#include <iostream>
#include <thread>
int counter = 0; // 共享数据
void increment() {
for (int i = 0; i < 100000; ++i) {
++counter; // 修改被共享的数据
}
}
int main() {
std::thread th1(increment);
std::thread th2(increment);
th1.join();
th2.join();
// 预期输出是200000,但由于数据竞争,实际输出可能小于这个值
std::cout << "Final counter value: " << counter << std::endl;
// Final counter value: 107194
return 0;
}
两个线程t1和t2可能同时对counter进行自增操作,导致counter的值增加的数量少于预期(即少于200000)。这是因为:
自增操作包含三个步骤:
- 从内存中读取counter的当前值0。
- 将这个值加1。
- 将结果1写回内存中的counter。
如果两个线程同时执行这些步骤,可能会发生以下情况:
- 线程t1读取counter的值为0。
- 线程t2也读取counter的值为0(因为线程t1还没有将新的值写回内存)。
- 线程t1将1写回counter。
- 线程t2也将1写回counter(因为它之前读取的值是0)。
C++互斥锁
互斥锁(互斥量)能保护多个线程的共享资源不被同时访问。互斥锁的状态只有两种:开锁(unlocked)和闭锁(locked)。当一个任务(或线程)持有互斥锁,且该互斥锁处于闭锁状态,那么这个任务可以执行访问共享资源的代码,其他任务(或线程)则不被允许。开锁后,该线程将失去了对互斥锁的所有权,其他正在等待的线程现在有机会获取这个互斥锁,以访问共享资源。
在C++11中提供了std::mutex支持这一功能。std::mutex是一个简单的互斥锁类,它有两个主要操作:lock()和unlock(),用于实现闭锁和开锁。
lock():当一个线程调用 lock() 函数时,它会尝试获取与该函数关联的互斥锁。如果互斥量当前没有被其他线程锁定(即它是可用的),则调用线程会成功获取锁,并继续执行其后续代码。 如果互斥量已经被其他线程锁定,则调用线程会被阻塞(即它会停止执行,直到锁变得可用)。 一旦线程获取了锁,它就可以安全地访问共享资源,而不必担心其他线程同时修改它。
unlock(): 当一个线程完成对共享资源的访问后,它应该调用 unlock() 函数来释放与该函数关联的互斥量(或其他锁)。释放锁允许其他线程获取该锁并访问共享资源。
在C++中,直接使用 lock() 和 unlock() 函数来管理锁可能会导致一些问题,特别是当代码路径变得复杂时。为了简化锁的管理并减少出错的可能性,C++11引入了 std::lock_guard 和 std::unique_lock 等RAII(Resource Acquisition Is Initialization)风格的包装器,用于自动管理互斥锁的开锁和闭锁。其中,std::lock_guard 是最简单的锁包装器,它只提供了基本的锁定和解锁功能。 std::unique_lock 提供了比 std::lock_guard 更多的功能,更灵活,后续再单独介绍。
下面给出示例展示如何使用lock()和unlock()以及锁包装器std::lock_guard 进行互斥锁的闭锁和开锁。
lock() 和 unlock()
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mtx; // 创建一个互斥锁实例,用于保护共享数据
int counter = 0; // 共享数据
void increment() {
for (int i = 0; i < 100000; ++i) {
// 显式地调用了lock()和unlock()来分别获取和释放锁:
mtx.lock(); // 获取锁
++counter; // 递增操作
mtx.unlock(); // 释放锁
}
}
int main() {
std::thread th1(increment);
std::thread th2(increment);
th1.join();
th2.join();
std::cout << "Final counter value: " << counter << std::endl;
// Final counter value: 200000
return 0;
}
std::lock_guard
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mtx; // 互斥锁用于保护共享数据
int counter = 0; // 共享数据
void increment() {
std::unique_lock<std::mutex> lock(mtx); // 构造时自动获取锁
for (int i = 0; i < 100000; ++i) {
++counter; // 递增操作,因为lock存在,所以此操作是安全的
}
// lock在离开作用域时自动释放锁
}
int main() {
std::thread th1(increment);
std::thread th2(increment);
th1.join();
th2.join();
std::cout << "Final counter value: " << counter << std::endl;
// Final counter value: 200000
return 0;
}
猜你喜欢
- 2024-10-12 大牛巧用一文带你彻底搞懂解释器的内部构造和解释执行过程
- 2024-10-12 JAVA中锁的深入理解与解析 java 锁的是什么
- 2024-10-12 C++核心准则CP.44:记得为lock_guards和unique_locks命名
- 2024-10-12 深入JVM锁机制1-synchronized jvm的锁
- 2024-10-12 一文搞懂Linux线程同步原理 linux多线程同步机制
- 2024-10-12 C语言中的并发编程技巧:提高程序的并行性和效率
- 2024-10-12 如何使用C语言进行并发编程? c并发编程实战 中文版 pdf
- 2024-10-12 C++20 新特性(15):协程(Coroutines )
- 2024-10-12 Go中读写锁RWMutex的基本用法 go 读写锁
- 2024-10-12 深入并发锁,解析Synchronized锁升级
你 发表评论:
欢迎- 最近发表
- 标签列表
-
- oraclesql优化 (66)
- 类的加载机制 (75)
- feignclient (62)
- 一致性hash算法 (71)
- dockfile (66)
- 锁机制 (57)
- javaresponse (60)
- 查看hive版本 (59)
- phpworkerman (57)
- spark算子 (58)
- vue双向绑定的原理 (68)
- springbootget请求 (58)
- docker网络三种模式 (67)
- spring控制反转 (71)
- data:image/jpeg (69)
- base64 (69)
- java分页 (64)
- kibanadocker (60)
- qabstracttablemodel (62)
- java生成pdf文件 (69)
- deletelater (62)
- com.aspose.words (58)
- android.mk (62)
- qopengl (73)
- epoch_millis (61)
本文暂时没有评论,来添加一个吧(●'◡'●)