C++11 同步与互斥
C++11 同步与互斥
0. C++11的线程
#include <thread>
- 面向对象的接口
- RAII(资源获取即初始化)机制,当线程对象被销毁时,会自动执行析构函数,如果线程仍然在运行,会自动调用std::terminate()来终止程序;
- 使用
std::mutex
和std::condition_variable
进行同步:C++11线程库引入了互斥量(std::mutex)和条件变量(std::condition_variable),用于线程间的同步和通信。 - 可移植性:C++11线程库提供了跨平台的线程抽象,使得编写可移植的多线程代码更加容易。开发者不再需要处理底层的线程创建和管理细节,而是直接使用标准线程库提供的接口。
1. 互斥
1.1 锁是实现互斥的方法,在std中实现了多种基本锁如下:
std::mutex:最基本的互斥锁,只能在同一线程中进行加锁和解锁操作。
std::recursive_mutex:递归互斥锁,允许同一线程多次加锁,但必须在同一线程中解锁相同次数。
std::timed_mutex:定时互斥锁,允许线程尝试加锁一段时间,如果超时则放弃锁。
std::recursive_timed_mutex:递归定时互斥锁,结合了递归和定时互斥锁的特性。
std::shared_mutex:共享互斥锁,允许多个线程同时读取共享资源,但只允许一个线程写入共享资源。当一个线程持有写入权限时,其他所有线程都无法获取读取或写入权限,直到该线程释放写入权限。注意这是一个读写分离锁,即多个线程可以同时读取共享数据,但只有一个线程可以写入共享数据。当一个线程正在写入共享数据时,其他线程必须等待该线程完成写入操作后才能进行读取或写入操作。
std::shared_timed_mutex:定时共享互斥锁,结合了共享和定时互斥锁的特性。
这些锁类型都是线程安全的,可以在多线程环境下使用。互斥量的锁定和解锁操作是原子的,即它们在并发环境中是不可分割的操作,确保了线程之间的同步和互斥。这意味着在任何给定时间点,只有一个线程可以持有互斥量的锁,从而保证了共享资源的独占访问。
1.2 锁的封装
为了更方便的管理互斥锁的生命周期,具有RAII特性,即在对象构造时获取资源,在对象析构时释放资源,从而保证资源的正确获取和释放。他利用此原理在析构函数中释放了锁,即可以达到自动释放锁的效果。
std::unique_lock<std::mutex> lock(mutex_)
可以在构造函数中传入一个互斥锁对象,当
std::unique_lock
对象被销毁时(出了作用域会自动销毁),它会自动释放互斥锁。同时,std::unique_lock
还提供了lock()
和unlock()
方法,可以手动控制互斥锁的加锁和解锁。/*unique_lock简单实现*/
template<class Mutex>
class unique_lock {
public:
explicit unique_lock(Mutex& m) : mutex_(m) {
mutex_.lock();
}
~unique_lock() {
mutex_.unlock();
}
private:
Mutex& mutex_;
};
std::lock_guard<std::mutex> lock(mutex_)
.当std::lock_guard
对象被销毁时,互斥锁会自动解锁。这样可以确保在std::lock_guard
对象的生命周期内,互斥锁一直处于锁定状态,从而避免了忘记解锁的问题。std::scoped_lock
也是一个模板类,用于管理多个互斥锁。在创建std::scoped_lock
对象时,需要传入多个互斥锁对象,该对象会在std::scoped_lock
的构造函数中被锁定,当std::scoped_lock
对象被销毁时,所有互斥锁会自动解锁。这样可以确保在std::scoped_lock
对象的生命周期内,所有互斥锁都处于锁定状态,从而避免了死锁的问题。需要注意的是,std::scoped_lock
可以同时锁定多个互斥锁,但是不能锁定同一个互斥锁多次,否则会导致死锁。
2.同步机制
在linux中,可以用于实现同步的机制有条件变量,信号量,互斥锁,在C++11中的同步机制主要是通过条件变量来实现的,需要引入头文件condition_variable
,其主要方法有
wait(lock, predicate)
:lambda函数重载等待条件notify_one()
:至少唤醒一个线程notify_all()
:唤醒所有线程(如果可用资源不只有1个,竞争互斥锁失败的线程重新等待)
void WorkerThread()
{
std::cout << "WorkerThread: Started" << std::endl;
// 等待主线程发出信号
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, [] { return isReady; });
// 收到信号后执行任务
std::cout << "WorkerThread: Resumed" << std::endl;
std::cout << "WorkerThread: Performing task..." << std::endl;
// 执行任务的代码
std::cout << "WorkerThread: Completed" << std::endl;
}
int main()
{
// 创建工作线程
std::thread worker(WorkerThread);
// 发送信号给工作线程
{
std::lock_guard<std::mutex> lock(mtx);
isReady = true;
cv.notify_one();
}
// 等待工作线程完成
worker.join();
return 0;
}
可以使用条件变量与互斥锁实现信号量:
#include <mutex>
#include <condition_variable>
class Semaphore {
public:
Semaphore(int count = 0) : count_(count) {}
void Wait() {
std::unique_lock<std::mutex> lock(mutex_);
while (count_ == 0) {
condition_.wait(lock);
}
count_--;
}
void Signal() {
std::unique_lock<std::mutex> lock(mutex_);
count_++;
condition_.notify_one();
}
private:
int count_;
std::mutex mutex_;
std::condition_variable condition_;
};
3. 线程池
#include <iostream>
#include <thread>
#include <vector>
#include <queue>
#include <functional>
#include <mutex>
#include <condition_variable>
class ThreadPool {
public:
ThreadPool(size_t numThreads) : stop(false) {
for (size_t i = 0; i < numThreads; ++i) {
threads.emplace_back([this]() {
while (true) {
std::function<void()> task;
{
std::unique_lock<std::mutex> lock(queueMutex);
condition.wait(lock, [this]() {
return stop || !tasks.empty();
});
if (stop && tasks.empty()) {
return;
}
task = std::move(tasks.front());
tasks.pop();
}
task();
}
});
}
}
~ThreadPool() {
{
std::unique_lock<std::mutex> lock(queueMutex);
stop = true;
}
condition.notify_all();
for (std::thread& thread : threads) {
thread.join();
}
}
template<class F, class... Args>
void enqueue(F&& f, Args&&... args) {
{
std::unique_lock<std::mutex> lock(queueMutex);
tasks.emplace([=]() {
std::invoke(std::forward<F>(f), std::forward<Args>(args)...);
});
}
condition.notify_one();
}
private:
std::vector<std::thread> threads;
std::queue<std::function<void()>> tasks;
std::mutex queueMutex;
std::condition_variable condition;
bool stop;
};
// 示例任务函数
void taskFunction(int id) {
std::cout << "Task " << id << " is running." << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout << "Task " << id << " is done." << std::endl;
}
int main() {
ThreadPool threadPool(4);
// 添加任务到线程池
for (int i = 0; i < 8; ++i) {
threadPool.enqueue(taskFunction, i);
}
// 等待任务执行完毕
std::this_thread::sleep_for(std::chrono::seconds(5));
return 0;
}
这个地方可能会有一些拓展问题:
- 如何安排线程优先级?
- 线程异常时该怎么做?
C++11 同步与互斥的更多相关文章
- C++ 11 线程的同步与互斥
这次写的线程的同步与互斥,不依赖于任何系统,完全使用了C++11标准的新特性来写的,就连线程函数都用了C++11标准的lambda表达式. /* * thread_test.cpp * * Copyr ...
- 转载自~浮云比翼:Step by Step:Linux C多线程编程入门(基本API及多线程的同步与互斥)
Step by Step:Linux C多线程编程入门(基本API及多线程的同步与互斥) 介绍:什么是线程,线程的优点是什么 线程在Unix系统下,通常被称为轻量级的进程,线程虽然不是进程,但却可 ...
- Step by Step:Linux C多线程编程入门(基本API及多线程的同步与互斥)
介绍:什么是线程,线程的优点是什么 线程在Unix系统下,通常被称为轻量级的进程,线程虽然不是进程,但却可以看作是Unix进程的表亲,同一进程中的多条线程将共享该进程中的全部系统资源,如虚拟地址空间, ...
- exec函数族,守护进程,线程同步和互斥
2015.3.2 进程和程序有三点不同:1,存在位置不同,程序:硬盘,磁盘.进程:内存2. 程序是静态的,进程是动态的 执行./a.out -->bash->bash程序调用fork()- ...
- Windows下C++多线程同步与互斥简单运用
1. 互斥量,Mutex #include <Windows.h> #include <iostream> using namespace std; DWORD WINAPI ...
- UNIX环境高级编程——线程同步之互斥量
互斥量(也称为互斥锁)出自POSIX线程标准,可以用来同步同一进程中的各个线程.当然如果一个互斥量存放在多个进程共享的某个内存区中,那么还可以通过互斥量来进行进程间的同步. 互斥量,从字面上就可以知道 ...
- java同步和互斥【用具体程序说明】
java同步和互斥[用具体程序说明] 所有对象都自动含有单一的锁,也就是所有对象都有且只有唯一的锁,所以当某个任务(线程)访问一个类A中含有sycnhronized的方法是,那么 ...
- Linux驱动之同步、互斥、阻塞的应用
同步.互斥.阻塞的概念: 同步:在并发程序设计中,各进程对公共变量的访问必须加以制约,这种制约称为同步. 互斥机制:访问共享资源的代码区叫做临界区,这里的共享资源可能被多个线程需要,但这些共享资源又不 ...
- c++11 线程的互斥量
c++11 线程的互斥量 为什么需要互斥量 在多任务操作系统中,同时运行的多个任务可能都需要使用同一种资源.这个过程有点类似于,公司部门里,我在使用着打印机打印东西的同时(还没有打印完),别人刚好也在 ...
- python-Lock锁线程同步和互斥
#!/usr/bin/python #coding=utf-8 #线程间通信的同步与互斥操作-锁 import threading a=b=0 lock=threading.Lock() def va ...
随机推荐
- MinIO客户端之cp
MinIO提供了一个命令行程序mc用于协助用户完成日常的维护.管理类工作. 官方资料 mc cp 上传文件至指定桶内,命令如下: ./mc cp ./local.json local1/bkt1/ 控 ...
- mv 用法
ls #查看目录下所有文件 mkdir 11 #创建11文件夹 mv * 11/ #把所有文件移动到11文件夹下 cd 11 #进入11文件夹 ls #查看文件夹里的内容 mv * ../ #把11文 ...
- 如何从零开始实现TDOA技术的 UWB 精确定位系统(3)
这是一个系列文章<如何从零开始实现TDOA技术的 UWB 精确定位系统>第3部分. 重要提示(劝退说明): Q:做这个定位系统需要基础么?A:文章不是写给小白看的,需要有电子技术和软件 ...
- Next.js 开发指南 路由篇 | App Router
前言 路由(routers)是应用的重要组成部分.所谓路由,有多种定义,对于应用层的单页应用程序而言,路由是一个决定 URL 如何呈现的库,在服务层实现 API 时,路由是解析请求并将请求定向到处理程 ...
- 调用含有header的WebService时,跳过证书验证
最近在做Webservice的接口调用,header的用户名和密码都是正确的,地址也是对的,但一直提示:基础连接已关闭,未能与SSL/TLS安全通道建立信任关系. 解决方案: 请求之前加上下面得代码即 ...
- 【华为云技术分享】空间异常即刻诊断,华为云数据管理服务DAS又出新招
摘要:华为云数据管理服务DAS提供的云DBA智能化运维平台于日前发布了空间&元数据分析特性,支持查看实例空间异常列表.实例空间概况.数据库列表信息,帮助用户及时发现数据库中的空间和元数据异常, ...
- 5大特性,带你认识化繁为简的华为云CodeArts Deploy
摘要:2月27日,华为云发布持续部署服务CodeArts Deploy,通过模块化自由编排部署流程,实现软件的自动化部署,帮助企业软件产品的快速.高效.高质量交付. 本文分享自华为云社区<化繁为 ...
- CodeArts TestPlan:一站式测试管理平台
摘要:华为云正式发布CodeArts TestPlan,这是一款自主研发的一站式测试管理平台,沉淀了华为30多年高质量的软件测试工程方法与实践,覆盖测试计划.测试设计.测试执行和测试评估等全流程. 本 ...
- Spark 开源新特性:Catalyst 优化流程裁剪
摘要:为了解决过多依赖 Hive 的问题, SparkSQL 使用了一个新的 SQL 优化器替代 Hive 中的优化器, 这个优化器就是 Catalyst. 本文分享自华为云社区<Spark 开 ...
- 你知道,什么时候用Vue计算属性吗?
摘要:当我们处理复杂逻辑时,都应该使用计算属性. 本文分享自华为云社区<深入理解计算属性,知道什么时候该用Vue计算属性吗?>,作者: 前端老实人 . 计算属性 有些时候,我们在模板中放入 ...