整理自: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 的所有权。

1) 若 sizeof...(MutexTypes) == 0 则不做任何事。否则若 sizeof...(MutexTypes) == 1 ,则等效地调用 m.lock() 。否则,等效地调用 std::lock(m...) 。若 MutexTypes 之一不是递归互斥,且当前线程已占有 m... 中对应的参数,则行为未定义。
2) 取得互斥 m... 的所有权而试图不锁定任何互斥。若当前线程已占有 m... 中所有互斥,则行为未定义。
3) 复制构造函数被删除。

若 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)的更多相关文章

  1. Java基础教程:多线程基础(6)——信号量(Semaphore)

    Java基础教程:多线程基础(6)——信号量(Semaphore) 信号量 信号量(Semaphore)由一个值和一个指针组成,指针指向等待该信号量的进程.信号量的值表示相应资源的使用情况.信号量S≥ ...

  2. C++多线程基础教程

    目录 1 什么是C++多线程? 2 C++多线程基础知识 2.1 创建线程 2.2 互斥量使用 lock()与unlock(): lock_guard(): unique_lock: conditio ...

  3. Java基础知识笔记(四:多线程基础及生命周期)

    一.多线程基础 编写线程程序主要是构造线程类.构造线程类的方式主要有两种,一种是通过构造类java.lang.Thread的子类,另一种是通过构造方法实现接口java.lang.Runnable的类. ...

  4. Java多线程干货系列—(一)Java多线程基础

    前言 多线程并发编程是Java编程中重要的一块内容,也是面试重点覆盖区域,所以学好多线程并发编程对我们来说极其重要,下面跟我一起开启本次的学习之旅吧. 正文 线程与进程 1 线程:进程中负责程序执行的 ...

  5. JAVASE02-Unit010: 多线程基础 、 TCP通信

    多线程基础 . TCP通信 * 当一个方法被synchronized修饰后,那么 * 该方法称为同步方法,即:多个线程不能同时 * 进入到方法内部执行. package day10; /** * 当多 ...

  6. JAVASE02-Unit09: 多线程基础

    Unit09: 多线程基础 * 线程 * 线程用于并发执行多个任务.感官上像是"同时"执行 *  * 创建线程有两种方式. * 方式一: * 继承线程并重写run方法来定义线程要执 ...

  7. java多线程基础

    多线程基础 读书练习照猫画虎 package Threadtest; import java.util.Date; import java.util.concurrent.ArrayBlockingQ ...

  8. C#编程总结(二)多线程基础

    C#编程总结(二)多线程基础 无论您是为具有单个处理器的计算机还是为具有多个处理器的计算机进行开发,您都希望应用程序为用户提供最好的响应性能,即使应用程序当前正在完成其他工作.要使应用程序能够快速响应 ...

  9. swift开发多线程篇 - 多线程基础

    swift开发多线程篇 - 多线程基础 iOS 的三种多线程技术 (1)NSThread  使用NSThread对象建立一个线程非常方便 但是!要使用NSThread管理多个线程非常困难,不推荐使用 ...

随机推荐

  1. Python Twisted系列教程14:Deferred用于同步环境

    作者:dave@http://krondo.com/when-a-deferred-isnt/  译者:杨晓伟(采用意译) 你可以从这里从头开始阅读这个系列. 介绍 这部分我们要介绍Deferred的 ...

  2. http协议请求响应内容示例

    POST http://www.cytxl.com.cn/api/common/login.php?XDEBUG_SESSION_START=netbeans-xdebug HTTP/1.1 Host ...

  3. leetcode766

    本题经过一下午的思考,终于解出来了.使用的是层次遍历的思想. class Solution { public: bool isToeplitzMatrix(vector<vector<in ...

  4. MyBatis3配置文件示例及解释

    转自:https://blog.csdn.net/tobylxy/article/details/84320694 1 <?xml version="1.0" encodin ...

  5. 【开发工具】最强Git使用总结

    目录 必读材料 常用小结 Git操作流程 Git常用操作命令 - 代码提交和同步代码 Git常用操作命令 - 代码撤销和撤销同步 Git常用操作命令 - 其它常用命令 Git是分布式代码托管的标杆,这 ...

  6. MYSQL中str_to_date函数的用法

    str_to_date(str,format) 函数的用法 str_to_date函数将str转化为日期型的数据,format表示转化后的格式. format参数格式: 常用: %Y  年 %m  月 ...

  7. POJ3624(背包问题)

    1.题目链接地址 http://poj.org/problem?id=3624 2.源代码 #include<iostream> using namespace std; #define ...

  8. jquery 获取url 参数方法 以及 解决url中文问题

    //jQuery 动态给a 标签赋值 跳转 新的页面打开. /* <a class="btn btn-success" id="test" target= ...

  9. ubuntu 下正确安装android手机驱动

    1. 查看手机ID号. charlesxue@THSHIBA:~/setup/cocos2d-x/cocos2d-x-/projects/simpleGame/proj.android/bin$ ls ...

  10. sencha表单入门例子

    来自于<sencha touch 权威指南> ------------------------------- 一.网站结构 二.index.html代码 <!DOCTYPE HTML ...