c++多线程基础3(mutex)
整理自:zh.cppreference.com/w/cpp/thread
互斥锁
互斥算法避免多个线程同时访问共享资源。这会避免数据竞争,并提供线程间的同步支持。定义于头文件 <mutex>
互斥锁有可重入、不可重入之分。C++标准库中用 mutex 表示不可重入的互斥锁,用 recursive_mutex 表示可重入的互斥锁。为这两个类增加根据时间来阻塞线程的能力,就又有了两个新的互斥锁:timed_mutex(不可重入的锁)、recursive_timed_mutex(可重入的锁)
C++标准库的所有mutex都是不可拷贝的,也不可移动
std::mutex:
mutex
类是能用于保护共享数据免受从多个线程同时访问的同步原语。mutex
提供排他性非递归所有权语义。操作:
lock:如果 mutex 未上锁,则将其上锁。否则如果已经其它线程 lock,则阻塞当前线程
try_lock:如果 mutex 未上锁,则将其上锁。否则返回 false,并不阻塞当前线程
unlock:如果 mutex 被当前线程锁住,则将其解锁。否则,是未定义的行为
native_handle:返回底层实现定义的线程句柄
注意:std::mutex
既不可复制亦不可移动
例1:
#include <iostream>
#include <chrono>
#include <thread>
#include <mutex>
using namespace std; int g_num = ;//为 g_num_mutex 所保护
std::mutex g_num_mutex; void slow_increment(int id) {
for(int i = ; i < ; ++i) {
g_num_mutex.lock();
++g_num;
cout << id << " => " << g_num << endl;
g_num_mutex.unlock(); std::this_thread::sleep_for(std::chrono::seconds());
}
} int main(void) {
std::thread t1(slow_increment, );
std::thread t2(slow_increment, );
t1.join();
t2.join(); // 输出:
// 0 => 1
// 1 => 2
// 0 => 3
// 1 => 4
// 0 => 5
// 1 => 6 return ;
}
例2:
#include <iostream>
#include <chrono>
#include <mutex>
#include <thread>
using namespace std; std::chrono::milliseconds interval();
std::mutex mtex;
int job_shared = ;//两个线程都能修改,mtex将保护此变量
int job_exclusive = ;//只有一个线程能修改 //此线程能修改 jon_shared 和 job_exclusive
void job_1() {
std::this_thread::sleep_for(interval);//令job_2持锁 while(true) {
//尝试锁定 mtex 以修改 job_shared
if(mtex.try_lock()) {
cout << "job shared (" << job_shared << ")\n";
mtex.unlock();
return;
} else {
//不能修改 job_shared
++job_exclusive;
cout << "job exclusive (" << job_exclusive << ")\n";
std::this_thread::sleep_for(interval);
}
}
} // 此线程只能修改 job_shared
void job_2() {
mtex.lock();
std::this_thread::sleep_for( * interval);
++job_shared;
mtex.unlock();
} int main(void) {
std::thread t1(job_1);
std::thread t2(job_2);
t1.join();
t2.join(); // 输出:
// job exclusive (1)
// job exclusive (2)
// job exclusive (3)
// job exclusive (4)
// job shared (1) return ;
}
std::timed_mutex:
timed_mutex
类是能用于保护数据免受多个线程同时访问的同步原语。
以类似 mutex 的行为, timed_mutex
提供排他性非递归所有权语义。另外,timed_mutex 在 mutex 的基础上增加了以下两个操作:
try_lock_for():
函数原型:template< class Rep, class Period >
bool try_lock_for( const std::chrono::duration<Rep,Period>& timeout_duration );
尝试锁互斥。阻塞直到经过指定的 timeout_duration
或得到锁,取决于何者先到来。成功获得锁时返回 true , 否则返回 false 。若 timeout_duration
小于或等于 timeout_duration.zero()
,则函数表现同 try_lock() 。由于调度或资源争议延迟,此函数可能阻塞长于 timeout_duration
。
标准推荐用 steady_clock 度量时长。若实现用 system_clock 代替,则等待时间亦可能对时钟调整敏感。
同 try_lock() ,允许此函数虚假地失败并返回 false ,即使在 timeout_duration
中某点互斥不为任何线程所锁定。
若此操作返回 true ,则同一互斥上先前的 unlock() 调用同步于(定义于 std::memory_order )它。若已占有 mutex
的线程调用 try_lock_for
,则行为未定义。
try_lock_until(time_point):
函数原型:template< class Clock, class Duration >
bool try_lock_until( const std::chrono::time_point<Clock,Duration>& timeout_time );
尝试所互斥。阻塞直至抵达指定的 timeout_time
或得到锁,取决于何者先到来。成功获得锁时返回 true ,否则返回 false 。若已经过 timeout_time
,则此函数表现同 try_lock() 。
使用倾向于 timeout_time
的时钟,这表示时钟调节有影响。从而阻塞的最大时长可能小于但不会大于在调用时的 timeout_time - Clock::now() ,依赖于调整的方向。由于调度或资源争议延迟,函数亦可能阻塞长于抵达 timeout_time
之后。同 try_lock() ,允许此函数虚假地失败并返回 false ,即使在 timeout_time
前的某点任何线程都不锁定互斥。
若此操作返回 true ,则同一互斥上先前的 unlock() 调用同步于(定义于 std::memory_order )它。
若已占有 mutex
的线程调用 try_lock_until
,则行为未定义。
try_lock_for / until可以检测到死锁的出现:
if(!try_lock_for(chrono::hours()))
{
throw "出现死锁!";
}
例1:
#include <iostream>
#include <mutex>
#include <thread>
#include <vector>
#include <sstream> std::mutex cout_mutex; // 控制到 std::cout 的访问
std::timed_mutex mutex; void job(int id)
{
using Ms = std::chrono::milliseconds;
std::ostringstream stream; for (int i = ; i < ; ++i) {
if (mutex.try_lock_for(Ms())) {
stream << "success ";
std::this_thread::sleep_for(Ms());
mutex.unlock();
} else {
stream << "failed ";
}
std::this_thread::sleep_for(Ms());
} std::lock_guard<std::mutex> lock(cout_mutex);
std::cout << "[" << id << "] " << stream.str() << "\n";
} int main()
{
std::vector<std::thread> threads;
for (int i = ; i < ; ++i) {
threads.emplace_back(job, i);
} for (auto& i: threads) {
i.join();
} // 输出:
// [0] failed failed failed
// [3] failed failed success
// [2] failed success failed
// [1] success failed success return ;
}
例2:
#include <thread>
#include <iostream>
#include <chrono>
#include <mutex> std::timed_mutex test_mutex; void f()
{
auto now=std::chrono::steady_clock::now();
test_mutex.try_lock_until(now + std::chrono::seconds());
std::cout << "hello world\n";
} int main()
{
std::lock_guard<std::timed_mutex> l(test_mutex);
std::thread t(f);
t.join(); return ;
}
递归锁:
在同一个线程中连续 lock 两次 mutex 会产生死锁:
一般情况下,如果同一个线程先后两次调用 lock,在第二次调⽤用时,由于锁已经被占用,该线程会挂起等待占用锁的线程释放锁,然而锁正是被自己占用着的,该线程又被挂起而没有机会释放锁,因此 就永远处于挂起等待状态了,于是就形成了死锁(Deadlock):
#include<iostream> //std::cout
#include<thread> //std::thread
#include<mutex> //std::mutex
using namespace std;
mutex g_mutex; void threadfun1()
{
cout << "enter threadfun1" << endl;
// lock_guard<mutex> lock(g_mutex);
g_mutex.lock();
cout << "execute threadfun1" << endl;
g_mutex.unlock();
} void threadfun2()
{
cout << "enter threadfun2" << endl;
// lock_guard<mutex> lock(g_mutex);
g_mutex.lock();
threadfun1();
cout << "execute threadfun2" << endl;
g_mutex.unlock();
} int main()
{
threadfun2(); //死锁
return ;
} // 运行结果:
// enter threadfun2
// enter threadfun1
//就会产生死锁
此时就需要使用递归式互斥量 recursive_mutex 来避免这个问题。recursive_mutex 不会产生上述的死锁问题,只是是增加锁的计数,但必须确保你 unlock 和 lock 的次数相同,其他线程才可能锁这个 mutex:
#include<iostream> //std::cout
#include<thread> //std::thread
#include<mutex> //std::mutex
using namespace std; recursive_mutex g_rec_mutex; void threadfun1()
{
cout << "enter threadfun1" << endl;
lock_guard<recursive_mutex> lock(g_rec_mutex);
cout << "execute threadfun1" << endl;
} void threadfun2()
{
cout << "enter threadfun2" << endl;
lock_guard<recursive_mutex> lock(g_rec_mutex);
threadfun1();
cout << "execute threadfun2" << endl;
} int main()
{
threadfun2(); //利用递归式互斥量来避免这个问题
return ;
}
// 运行结果:
// enter threadfun2
// enter threadfun1
// execute threadfun1
// execute threadfun2
recursive_mutex、recursive_timed_mutex 与对应的 mutex、timed_mutex 操作一致。不同点在于,非递归锁在 lock 或 try_lock 一个已经被当前线程 lock 的锁时会导致死锁,而递归锁不会
共享锁:
std::shared_timed_mutex(c++14起)
shared_mutex
类是能用于保护数据免受多个线程同时访问的同步原语。与其他促进排他性访问的互斥类型相反, shared_mutex 拥有二个层次的访问:
- 共享 - 多个线程能共享同一互斥的所有权。
- 排他性 - 仅一个线程能占有互斥。
共享互斥通常用于多个读线程能同时访问同一资源而不导致数据竞争,但只有一个写线程能访问的情形:
#include <iostream>
#include <mutex> // 对于 std::unique_lock
#include <shared_mutex>
#include <thread> class ThreadSafeCounter {
public:
ThreadSafeCounter() = default; // 多个线程/读者能同时读计数器的值。
unsigned int get() const {
std::shared_lock<std::shared_timed_mutex> lock(mutex_);//shared_lock 作用类似于 lock_guard
return value_;
} // 只有一个线程/写者能增加/写线程的值。
void increment() {
std::unique_lock<std::shared_timed_mutex> lock(mutex_);
value_++;
} // 只有一个线程/写者能重置/写线程的值。
void reset() {
std::unique_lock<std::shared_timed_mutex> lock(mutex_);
value_ = ;
} private:
mutable std::shared_timed_mutex mutex_;
unsigned int value_ = ;
}; int main() {
ThreadSafeCounter counter; auto increment_and_print = [&counter]() {
for (int i = ; i < ; i++) {
counter.increment();
std::cout << std::this_thread::get_id() << ' ' << counter.get() << '\n'; // 注意:写入 std::cout 实际上也要由另一互斥同步。省略它以保持示例简洁。
}
}; std::thread thread1(increment_and_print);
std::thread thread2(increment_and_print); thread1.join();
thread2.join(); // 输出:
// 2 1
// 3 2
// 2 3
// 3 4
// 2 5
// 3 6 return ;
}
std::shared_mutex(c++17起)
以类似 timed_mutex 的行为, shared_timed_mutex
提供通过 try_lock_for() 、 try_lock_until() 、 try_lock_shared_for() 、 try_lock_shared_until() 方法,试图带时限地要求 shared_timed_mutex
所有权的能力。std::shared_mutex 则恰好相反
通用互斥管理:
定义于头文件 <mutex>
std::lock_guard:
类 lock_guard
是互斥封装器,为在作用域块期间占有互斥提供便利 RAII 风格机制。
创建 lock_guard
对象时,它试图接收给定互斥的所有权。控制离开创建 lock_guard
对象的作用域时,销毁 lock_guard
并释放互斥。
lock_guard
类不可复制
要锁定的互斥,类型必须满足基础可锁要求
代码:
#include <thread>
#include <mutex>
#include <iostream> int g_i = ;
std::mutex g_i_mutex; // 保护 g_i void safe_increment()
{
std::lock_guard<std::mutex> lock(g_i_mutex);
++g_i; std::cout << std::this_thread::get_id() << ": " << g_i << '\n'; // g_i_mutex 在锁离开作用域时自动释放
} int main()
{
std::cout << "main: " << g_i << '\n'; std::thread t1(safe_increment);
std::thread t2(safe_increment); t1.join();
t2.join(); std::cout << "main: " << g_i << '\n'; // 输出:
// main: 0
// 2: 1
// 3: 2
// main: 2 return ;
}
std::scoped_lock(c++17起):
类 scoped_lock
是提供便利 RAII 风格机制的互斥包装器,它在作用域块的存在期间占有一或多个互斥。
创建 scoped_lock
对象时,它试图取得给定互斥的所有权。控制离开创建 scoped_lock
对象的作用域时,析构 scoped_lock
并以逆序释放互斥。若给出数个互斥,则使用免死锁算法,如同以 std::lock 。
scoped_lock
类不可复制
要锁的互斥类型必须满足可锁要求
构造函数:
explicit scoped_lock( MutexTypes&... m );
scoped_lock( std::adopt_lock_t, MutexTypes&... m );
scoped_lock( const scoped_lock& ) = delete;
取得给定互斥 m
的所有权。
MutexTypes
之一不是递归互斥,且当前线程已占有 m... 中对应的参数,则行为未定义。m...
中所有互斥,则行为未定义。若 m
在 scoped_lock
对象之前被销毁,则行为未定义
要获得其所有权的互斥
代码:
#include <mutex>
#include <thread>
#include <iostream>
#include <vector>
#include <functional>
#include <chrono>
#include <string> struct Employee {
Employee(std::string id) : id(id) {}
std::string id;
std::vector<std::string> lunch_partners;
std::mutex m;
std::string output() const
{
std::string ret = "Employee " + id + " has lunch partners: ";
for( const auto& partner : lunch_partners )
ret += partner + " ";
return ret;
}
}; void send_mail(Employee &, Employee &)
{
// 模拟耗时的发信操作
std::this_thread::sleep_for(std::chrono::seconds());
} void assign_lunch_partner(Employee &e1, Employee &e2)
{
static std::mutex io_mutex;
{
std::lock_guard<std::mutex> lk(io_mutex);
std::cout << e1.id << " and " << e2.id << " are waiting for locks" << std::endl;
} {
// 用 std::scoped_lock 取得二个锁,而无需担心
// 其他对 assign_lunch_partner 的调用死锁我们
// 而且它亦提供便利的 RAII 风格机制 std::scoped_lock lock(e1.m, e2.m); // 等价代码 1 (用 std::lock 和 std::lock_guard )
// std::lock(e1.m, e2.m);
// std::lock_guard<std::mutex> lk1(e1.m, std::adopt_lock);
// std::lock_guard<std::mutex> lk2(e2.m, std::adopt_lock); // 等价代码 2 (若需要 unique_lock ,例如对于条件变量)
// std::unique_lock<std::mutex> lk1(e1.m, std::defer_lock);
// std::unique_lock<std::mutex> lk2(e2.m, std::defer_lock);
// std::lock(lk1, lk2);
{
std::lock_guard<std::mutex> lk(io_mutex);
std::cout << e1.id << " and " << e2.id << " got locks" << std::endl;
}
e1.lunch_partners.push_back(e2.id);
e2.lunch_partners.push_back(e1.id);
} send_mail(e1, e2);
send_mail(e2, e1);
} int main()
{
Employee alice("alice"), bob("bob"), christina("christina"), dave("dave"); // 在并行线程中指派,因为就午餐指派发邮件消耗很长时间
std::vector<std::thread> threads;
threads.emplace_back(assign_lunch_partner, std::ref(alice), std::ref(bob));
threads.emplace_back(assign_lunch_partner, std::ref(christina), std::ref(bob));
threads.emplace_back(assign_lunch_partner, std::ref(christina), std::ref(alice));
threads.emplace_back(assign_lunch_partner, std::ref(dave), std::ref(bob)); for (auto &thread : threads) thread.join();
std::cout << alice.output() << '\n' << bob.output() << '\n'
<< christina.output() << '\n' << dave.output() << '\n';
}
blog.csdn.net/zouxinfox/article/details/5848519
unique_lock
类 unique_lock 可移动,但不可复制——它满足可移动构造 (MoveConstructible
) 和可移动赋值 (MoveAssignable
) 但不满足可复制构造 (CopyConstructible
) 或可复制赋值 (CopyAssignable
) 。类 unique_lock 是通用互斥包装器,允许延迟锁定、锁定的有时限尝试、递归锁定、所有权转移和与条件变量一同使用。
类 unique_lock 满足基础可锁 (BasicLockable
) 要求。若 Mutex 满足可锁 (Lockable
) 要求,则 unique_lock 亦满足可锁 (Lockable
) 要求(例如:能用于 std::lock ) ;若 Mutex 满足定时可锁 (TimedLockable
) 要求,则 unique_lock 亦满足定时可锁 (TimedLockable
) 要求
要锁定的互斥类型。类型必须满足基础可锁 (BasicLockable
) 要求
代码:
#include <mutex>
#include <thread>
#include <chrono> struct Box {
explicit Box(int num) : num_things{num} {} int num_things;
std::mutex m;
}; void transfer(Box &from, Box &to, int num)
{
// 仍未实际取锁
std::unique_lock<std::mutex> lock1(from.m, std::defer_lock);
std::unique_lock<std::mutex> lock2(to.m, std::defer_lock); // 锁两个 unique_lock 而不死锁
std::lock(lock1, lock2); from.num_things -= num;
to.num_things += num; // 'from.m' 与 'to.m' 互斥解锁于 'unique_lock' 析构函数
} int main()
{
Box acc1();
Box acc2(); std::thread t1(transfer, std::ref(acc1), std::ref(acc2), );
std::thread t2(transfer, std::ref(acc2), std::ref(acc1), ); t1.join();
t2.join();
}
关于 lock_guard 和 unique_lock:
http://lib.csdn.net/article/cplusplus/29099
http://blog.csdn.net/daaikuaichuan/article/details/71022824
c++多线程基础3(mutex)的更多相关文章
- Java基础教程:多线程基础(6)——信号量(Semaphore)
Java基础教程:多线程基础(6)——信号量(Semaphore) 信号量 信号量(Semaphore)由一个值和一个指针组成,指针指向等待该信号量的进程.信号量的值表示相应资源的使用情况.信号量S≥ ...
- C++多线程基础教程
目录 1 什么是C++多线程? 2 C++多线程基础知识 2.1 创建线程 2.2 互斥量使用 lock()与unlock(): lock_guard(): unique_lock: conditio ...
- Java基础知识笔记(四:多线程基础及生命周期)
一.多线程基础 编写线程程序主要是构造线程类.构造线程类的方式主要有两种,一种是通过构造类java.lang.Thread的子类,另一种是通过构造方法实现接口java.lang.Runnable的类. ...
- Java多线程干货系列—(一)Java多线程基础
前言 多线程并发编程是Java编程中重要的一块内容,也是面试重点覆盖区域,所以学好多线程并发编程对我们来说极其重要,下面跟我一起开启本次的学习之旅吧. 正文 线程与进程 1 线程:进程中负责程序执行的 ...
- JAVASE02-Unit010: 多线程基础 、 TCP通信
多线程基础 . TCP通信 * 当一个方法被synchronized修饰后,那么 * 该方法称为同步方法,即:多个线程不能同时 * 进入到方法内部执行. package day10; /** * 当多 ...
- JAVASE02-Unit09: 多线程基础
Unit09: 多线程基础 * 线程 * 线程用于并发执行多个任务.感官上像是"同时"执行 * * 创建线程有两种方式. * 方式一: * 继承线程并重写run方法来定义线程要执 ...
- java多线程基础
多线程基础 读书练习照猫画虎 package Threadtest; import java.util.Date; import java.util.concurrent.ArrayBlockingQ ...
- C#编程总结(二)多线程基础
C#编程总结(二)多线程基础 无论您是为具有单个处理器的计算机还是为具有多个处理器的计算机进行开发,您都希望应用程序为用户提供最好的响应性能,即使应用程序当前正在完成其他工作.要使应用程序能够快速响应 ...
- swift开发多线程篇 - 多线程基础
swift开发多线程篇 - 多线程基础 iOS 的三种多线程技术 (1)NSThread 使用NSThread对象建立一个线程非常方便 但是!要使用NSThread管理多个线程非常困难,不推荐使用 ...
随机推荐
- java成神之——Fork/Join基本使用
Fork/Join 大任务分小任务,小任务结果合并 ForkJoinPool pool = new ForkJoinPool(); RecursiveTask<Integer> task1 ...
- Tornado 高并发源码分析之四--- HTTPServer 与 TCPServer 对象
主要工作: 服务器启动的时候做的事: 1.把包含了各种配置信息的 application 对象封装到了 HttpServer 对象的 request_callback 字段中,等待被调用 2.TCPS ...
- Spring 快速入门
1.持久层 (1) 域模型层 (2) Dao 持久层接口 (3) DaoImpl 持久层接口实现 2.业务层 Service 业务接口层 ServiceImpl 业务接口实现 3.展现层 Sp ...
- [转] const T、const T*、T *const、const T&、const T*& 的区别
这里的T指的是一种数据类型,可以是int.long.doule等基本数据类型,也可以是自己类型的类型class.单独的一个const你肯定知道指的是一个常量,但const与其他类型联合起来的众多变化, ...
- Apache Hive 建表操作的简单描述
客户端连接hive [root@bigdata-02 bin]# ./beeline Beeline version by Apache Hive beeline: Connecting : Ente ...
- std:: lower_bound std:: upper_bound
std:: lower_bound 该函数返回范围内第一个不小于(大于或等于)指定val的值.如果序列中的值都小于val,则返回last.序列应该已经有序! eg: #include <iost ...
- 算法描述》LCA两三事(蒟蒻向)
LCA是图论中常用的解决树形结构子问题的工具,这一问题一般需要用一个简短的子函数直接解决,但是这对于广大蒟蒻们仍然是一个不小的问题. LCA是指在树形结构中两点的最近公共祖先,对于这个问题,直接向上找 ...
- 四.python数据类型,语句
Python基础 阅读: 120476 Python是一种计算机编程语言.计算机编程语言和我们日常使用的自然语言有所不同,最大的区别就是,自然语言在不同的语境下有不同的理解,而计算机要根据编程语言执行 ...
- Tensorflow学习(练习)—下载骨骼图像识别网络inception数据集
import tensorflow as tfimport osimport tarfileimport requests #inception模型下载地址inception_pretrain_mod ...
- java面试题 级hr解答 非技术问题 !=!=未看
Java基础 ● 集合类以及集合框架:HashMap与HashTable实现原理,线程安全性,hash冲突及处理算法:ConcurrentHashMap: ● 进程和线程的区别: ● Java的并发. ...