线程同步:互斥与条件变量

简介: 线程同步:互斥与条件变量

互斥量和条件变量:深入解析与代码实现

在并发编程中,互斥量和条件变量是两种非常重要的同步机制,它们分别用于保护共享资源不被多个线程同时访问,以及协调线程之间的执行顺序。本文将深入探讨互斥量和条件变量的工作原理,并通过代码示例展示如何在实际开发中使用它们。


一、互斥量(Mutex)

互斥量,也被称为互斥锁或互斥对象,是一种常用的同步原语,用于保护共享资源的访问。当一个线程获得互斥量的锁时,其他尝试获取该锁的线程将被阻塞,直到锁被释放。

在C++中,std::mutex是一个标准的互斥量实现。下面是一个简单的示例,展示了如何使用std::mutex来保护一个共享计数器:

#include <iostream>
#include <thread>
#include <vector>
#include <mutex>
std::mutex mtx; // 全局互斥量
int counter = 0; // 共享计数器
void increment(int id) {
mtx.lock(); // 请求互斥量的锁
++counter; // 修改共享资源
std::cout << "Thread " << id << " incremented counter to " << counter << std::endl;
mtx.unlock(); // 释放互斥量的锁
}
int main() {
std::vector<std::thread> threads;
const int num_threads = 10;
// 创建多个线程,每个线程都会调用increment函数
for (int i = 0; i < num_threads; ++i) {
threads.push_back(std::thread(increment, i));
}
// 等待所有线程完成
for (auto& th : threads) {
th.join();
}
std::cout << "Final counter value is " << counter << std::endl;
return 0;
}

在这个例子中,每个线程都会调用increment函数来增加共享计数器counter的值。通过使用mtx.lock()和mtx.unlock()来包裹对counter的访问,我们确保了每次只有一个线程能够修改counter的值。如果没有互斥量的保护,多个线程可能会同时修改counter,导致数据竞争和不一致的结果。


二、条件变量(Condition Variable

条件变量用于线程间的同步,允许一个或多个线程等待某个条件成立(通常由另一个线程触发)。条件变量通常与互斥量一起使用,以确保在检查条件或修改共享数据时不会发生数据竞争。

在C++中,std::condition_variable是一个标准的条件变量实现。下面是一个使用条件变量的示例,其中有一个生产者线程生产数据,一个消费者线程消费数据:

#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>
std::queue<int> data_queue; // 数据队列
std::mutex mtx; // 互斥量,保护数据队列
std::condition_variable cond_var; // 条件变量
bool stop = false; // 停止标志
void producer() {
for (int i = 0; i < 10; ++i) {
std::unique_lock<std::mutex> lock(mtx);
data_queue.push(i);
lock.unlock();
cond_var.notify_one(); // 通知一个等待的线程
std::this_thread::sleep_for(std::chrono::seconds(1));
}
{
std::lock_guard<std::mutex> lock(mtx);
stop = true;
}
cond_var.notify_all(); // 通知所有等待的线程
}
void consumer() {
while (true) {
std::unique_lock<std::mutex> lock(mtx);
cond_var.wait(lock, [] { return !data_queue.empty() || stop; }); // 等待数据可用或收到停止信号
if (stop && data_queue.empty()) {
break; // 所有数据已消费且收到停止信号,退出循环
}
int data = data_queue.front();
data_queue.pop();
lock.unlock();
std::cout << "Consumed: " << data << std::endl;
}
}
int main() {
std::thread producer_thread(producer);
std::thread consumer_thread(consumer);
producer_thread.join();
consumer_thread.join();
return 0;
}


在这个例子中,生产者线程producer将整数推送到data_queue中,然后通知条件变量cond_var。消费者线程consumer在一个循环中等待条件变量cond_var,直到队列非空或有停止信号为止。当队列非空时,消费者线程将取出数据并处理它。当所有数据都被消费且收到停止信号时,消费者线程将退出循环。

值得注意的是,我们在使用cond_var.wait()时传入了一个std::unique_lock对象,该对象在调用wait()时会自动释放互斥量,并在条件满足后重新获取,从而避免了死锁。同时,我们传递给wait()的Lambda表达式作为条件检查函数,确保只有在队列非空或接收到停止信号时线程才会被唤醒。

总结来说,互斥量和条件变量是并发编程中非常重要的同步工具。互斥量用于保护共享资源,确保同一时间只有一个线程能够访问它;而条件变量则用于协调线程间的执行顺序,允许线程等待某个条件成立。通过使用这些同步机制,我们可以编写出安全、可靠的并发代码。

相关文章
|
23天前
|
供应链 安全 NoSQL
PHP 互斥锁:如何确保代码的线程安全?
在多线程和高并发环境中,确保代码段互斥执行至关重要。本文介绍了 PHP 互斥锁库 `wise-locksmith`,它提供多种锁机制(如文件锁、分布式锁等),有效解决线程安全问题,特别适用于电商平台库存管理等场景。通过 Composer 安装后,开发者可以利用该库确保在高并发下数据的一致性和安全性。
35 6
|
4月前
|
Java 开发者
解锁并发编程新姿势!深度揭秘AQS独占锁&ReentrantLock重入锁奥秘,Condition条件变量让你玩转线程协作,秒变并发大神!
【8月更文挑战第4天】AQS是Java并发编程的核心框架,为锁和同步器提供基础结构。ReentrantLock基于AQS实现可重入互斥锁,比`synchronized`更灵活,支持可中断锁获取及超时控制。通过维护计数器实现锁的重入性。Condition接口允许ReentrantLock创建多个条件变量,支持细粒度线程协作,超越了传统`wait`/`notify`机制,助力开发者构建高效可靠的并发应用。
95 0
|
2月前
|
安全 Linux
Linux线程(十一)线程互斥锁-条件变量详解
Linux线程(十一)线程互斥锁-条件变量详解
|
3月前
|
存储 Java 程序员
优化Java多线程应用:是创建Thread对象直接调用start()方法?还是用个变量调用?
这篇文章探讨了Java中两种创建和启动线程的方法,并分析了它们的区别。作者建议直接调用 `Thread` 对象的 `start()` 方法,而非保持强引用,以避免内存泄漏、简化线程生命周期管理,并减少不必要的线程控制。文章详细解释了这种方法在使用 `ThreadLocal` 时的优势,并提供了代码示例。作者洛小豆,文章来源于稀土掘金。
|
3月前
|
存储 Ubuntu Linux
C语言 多线程编程(1) 初识线程和条件变量
本文档详细介绍了多线程的概念、相关命令及线程的操作方法。首先解释了线程的定义及其与进程的关系,接着对比了线程与进程的区别。随后介绍了如何在 Linux 系统中使用 `pidstat`、`top` 和 `ps` 命令查看线程信息。文档还探讨了多进程和多线程模式各自的优缺点及适用场景,并详细讲解了如何使用 POSIX 线程库创建、退出、等待和取消线程。此外,还介绍了线程分离的概念和方法,并提供了多个示例代码帮助理解。最后,深入探讨了线程间的通讯机制、互斥锁和条件变量的使用,通过具体示例展示了如何实现生产者与消费者的同步模型。
|
5月前
|
存储 SQL Java
(七)全面剖析Java并发编程之线程变量副本ThreadLocal原理分析
在之前的文章:彻底理解Java并发编程之Synchronized关键字实现原理剖析中我们曾初次谈到线程安全问题引发的"三要素":多线程、共享资源/临界资源、非原子性操作,简而言之:在同一时刻,多条线程同时对临界资源进行非原子性操作则有可能产生线程安全问题。
100 1
|
4月前
|
Java C语言 C++
并发编程进阶:线程同步与互斥
并发编程进阶:线程同步与互斥
47 0
|
5月前
|
存储 Python 容器
Node中的AsyncLocalStorage 使用问题之在Python中,线程内变量的问题如何解决
Node中的AsyncLocalStorage 使用问题之在Python中,线程内变量的问题如何解决
|
4月前
|
算法 Java 调度
【多线程面试题二十】、 如何实现互斥锁(mutex)?
这篇文章讨论了在Java中实现互斥锁(mutex)的两种方式:使用`synchronized`关键字进行块结构同步,以及使用`java.util.concurrent.locks.Lock`接口进行非块结构同步,后者提供了更灵活的同步机制和扩展性。
|
5月前
|
存储 缓存 算法
同时使用线程本地变量以及对象缓存的问题
【7月更文挑战第15天】同时使用线程本地变量和对象缓存需小心处理以避免数据不一致、竞争条件及内存泄漏等问题。线程本地变量使各线程拥有独立存储,但若与对象缓存关联,可能导致多线程环境下访问旧数据。缺乏同步机制时,多线程并发修改缓存中的共享对象还会引起数据混乱。此外,若线程结束时未释放对象引用,可能导致内存泄漏。例如,在Web服务器场景下,若一更新缓存而另一线程仍获取旧数据,则可能返回错误信息;在图像处理应用中,若多线程无序修改算法对象则可能产生错误处理结果。因此,需确保数据一致性、避免竞争条件并妥善管理内存。