当前位置: 首页 > news >正文

C++ mutex 与 condition_variable

导语:整理以下多线程的互斥与同步。

mutex 使用起来很简单,需要处理一段临界区代码时,只要调用 lock 或 try_lock 即可。lock 和 unlock 是没有返回值的,try_lock 的返回值是一个 bool,它用于尝试锁定互斥,成功返回 true,失败返回 false。

#include <chrono>
#include <mutex>
#include <thread>
#include <iostream>
 
std::chrono::milliseconds interval(100);
 
std::mutex mutex;
int job_shared = 0;    // 两个线程都能修改 'job_shared',mutex 将保护此变量
int job_exclusive = 0; // 只有一个线程能修改 'job_exclusive'
 
// 此线程能修改 'job_shared' 和 'job_exclusive'
void job_1() {
    std::this_thread::sleep_for(interval); // 令 'job_2' 持锁
 
    while (true) {
        // 尝试锁定 mutex 以修改 'job_shared'
        if (mutex.try_lock()) {
            std::cout << "job shared (" << job_shared << ")\n";
            mutex.unlock();
            return;
        } else {
            // 不能获取锁以修改 'job_shared'
            // 但有其他工作可做
            ++job_exclusive;
            std::cout << "job exclusive (" << job_exclusive << ")\n";
            std::this_thread::sleep_for(interval);
        }
    }
}
 
// 此线程只能修改 'job_shared'
void job_2() {
    mutex.lock();
    std::this_thread::sleep_for(5 * interval);
    ++job_shared;
    mutex.unlock();
}
 
int main()  {
    std::thread thread_1(job_1);
    std::thread thread_2(job_2);
 
    thread_1.join();
    thread_2.join();
    return 0;
}

可能的输出结果:
job exclusive (1)
job exclusive (2)
job exclusive (3)
job exclusive (4)
job shared (1)

job_2 会先持有锁,所以 job_1 内的 while 循环会先递增 job_exclusive 的值,直到 job_2 处理完后释放锁,才会打印 job_shared 的值。

#include <iostream>
#include <string>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <chrono>
 
std::mutex m;
std::condition_variable cv;
std::string data;
bool ready = false;
bool processed = false;
 
void worker_thread() {
    // 等待直至 main() 发送数据
    std::unique_lock<std::mutex> lk(m);
    cv.wait(lk, []{return ready;});
 
    // 等待后,我们占有锁。
    std::cout << "Worker thread is processing data\n";
    data += " after processing";
 
    // 发送数据回 main()
    processed = true;
    std::cout << "Worker thread signals data processing completed\n";
 
    // 通知前完成手动解锁,以避免等待线程才被唤醒就阻塞(细节见 notify_one )
    lk.unlock();
    cv.notify_one();
}
 
int main() {
    std::thread worker(worker_thread);
    std::cout << "wait for a moment" << std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(1));
 
    data = "Example data";
    // 发送 ready 信号到 worker 线程
    {
        std::lock_guard<std::mutex> lk(m);
        ready = true;
        std::cout << "main() signals data ready for processing\n";
    }
    cv.notify_one();
 
    // 等候 worker
    {
        std::unique_lock<std::mutex> lk(m);
        cv.wait(lk, []{return processed;});
    }
    std::cout << "Back in main(), data = " << data << '\n';
 
    worker.join();
    return 0;
}

在 worker_thread 内获取了互斥锁,但是真正的执行要等到 main 函数中触发 notify_one 才真正开始。

#include <iostream>
#include <condition_variable>
#include <thread>
#include <chrono>
 
std::condition_variable cv;
std::mutex cv_m;
int i = 0;
bool done = false;
 
void waits() {
    std::unique_lock<std::mutex> lk(cv_m);
    std::cout << "Waiting... \n";
    cv.wait(lk, []{return i == 1;});
    std::cout << "...finished waiting. i == 1\n";
    done = true;
}
 
void signals() {
    std::this_thread::sleep_for(std::chrono::seconds(1));
    std::cout << "Notifying falsely...\n";
    cv.notify_one(); // 等待线程被通知 i == 0.
                     // cv.wait 唤醒,检查 i ,再回到等待
 
    std::unique_lock<std::mutex> lk(cv_m);
    i = 1;
    while (!done) 
    {
        std::cout << "Notifying true change...\n";
        lk.unlock();
        cv.notify_one(); // 等待线程被通知 i == 1 , cv.wait 返回
        std::this_thread::sleep_for(std::chrono::seconds(1));
        lk.lock();
    }
}
 
int main()
{
    std::thread t1(waits);
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
    std::thread t2(signals);
    t1.join(); 
    t2.join();
    return 0;
}

 

#include <iostream>
#include <atomic>
#include <condition_variable>
#include <thread>
#include <chrono>

using namespace std::chrono_literals;

std::condition_variable cv;
std::mutex cv_m;
int i;
 
void waits(int idx) {
    std::unique_lock<std::mutex> lk(cv_m);
    if(cv.wait_for(lk, idx*100ms, []{return i == 1;})) 
        std::cerr << "Thread " << idx << " finished waiting. i == " << i << '\n';
    else
        std::cerr << "Thread " << idx << " timed out. i == " << i << '\n';
}
 
void signals() {
    std::this_thread::sleep_for(120ms);
    std::cerr << "Notifying...\n";
    cv.notify_all();
    std::this_thread::sleep_for(100ms);
    {
        std::lock_guard<std::mutex> lk(cv_m);
        i = 1;
    }
    std::cerr << "Notifying again...\n";
    cv.notify_all();
}
 
int main() {
    std::thread t1(waits, 1), t2(waits, 2), t3(waits, 3), t4(signals);
    t1.join(); t2.join(), t3.join(), t4.join();
}

输出:
Thread 1 timed out. i == 0
Notifying...
Thread 2 timed out. i == 0
Notifying again...
Thread 3 finished waiting. i == 1

std::lock_gaurd 和 std::unique_lock 的区别

lock_guard:没有提供加锁和解锁的接口。通过构造函数和析构函数控制锁的作用范围,创造对象的时候加锁,离开作用域的时候解锁;

unique_lock:提供了 lock() 和 unlock() 接口,可以通过构造函数和析构函数控制锁的作用范围。在构造函数中延时加锁,可以在需要的时候手动加锁和解锁。在析构的时候,会根据当前状态来决定是否要进行解锁(lock_guard 就一定会解锁)。

#include <mutex>
#include <thread>
#include <iostream>
 
struct Box {
    explicit Box(int num) : num_things{num} {}
 
    int num_things;
    std::mutex m;
};
 
void transfer(Box &from, Box &to, int num) {
    // don't actually take the locks yet
    std::unique_lock lock1{from.m, std::defer_lock};
    std::unique_lock lock2{to.m, std::defer_lock};
 
    // lock both unique_locks without deadlock
    std::lock(lock1, lock2);
 
    from.num_things -= num;
    to.num_things += num;
 
    // 'from.m' and 'to.m' mutexes unlocked in 'unique_lock' dtors
}
 
int main() {
    Box acc1{100};
    Box acc2{50};
 
    std::thread t1{transfer, std::ref(acc1), std::ref(acc2), 10};
    std::thread t2{transfer, std::ref(acc2), std::ref(acc1), 5};
 
    t1.join();
    t2.join();
 
    std::cout << "acc1: " << acc1.num_things << "\\n"
                 "acc2: " << acc2.num_things << '\\n';
}
std::mutex mu;
void Test() {
    int b = 1;
    std::unique_lock<std::mutex> lock(mu, std::defer_lock);
    lock.lock();
    b++;
    lock.unlock();
    cout << "b: " << b << endl;
}

使用 unique_lock 时,设置 std::defer_lock 才能手动加锁,没有设置则不能。延迟加锁的设置不会影响解锁,不管有没有设置都可以手动解锁。而 lock_guard 则没有加锁和解锁的接口。 

std::shared_mutex

shared_mutex 是 C++ 的原生读写锁实现,有共享和独占两种锁模式,适用于并发高的读场景下,通过 reader 之前共享锁来提升性能。在 C++17 之前,只能自己通过独占锁和条件变量自己实现读写锁或使用 C++14 加入的性能较差的 std::shared_timed_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_mutex> lock(mutex_);
    return value_;
  }
 
  // 只有一个线程/写者能增加/写线程的值。
  void increment() {
    std::unique_lock<std::shared_mutex> lock(mutex_);
    value_++;
    // 可以在程序结尾处手动解锁
  }
 
  // 只有一个线程/写者能重置/写线程的值。
  void reset() {
    std::unique_lock<std::shared_mutex> lock(mutex_);
    value_ = 0;
  }
 
 private:
  mutable std::shared_mutex mutex_;
  unsigned int value_ = 0;
};
 
int main() {
  ThreadSafeCounter counter;
  std::mutex mutex_;
  auto increment_and_print = [&counter, &mutex_]() {
    for (int i = 0; i < 3; i++) {
      counter.increment();
      std::lock_guard<std::mutex> lock(mutex_);
      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();
}

相关文章:

  • 基础 | Spring - [单例创建过程]
  • K8S集群Pod资源自动扩缩容方案
  • SPPNet
  • java多线程-多线程技能
  • 网课查题接口 该怎么搭建
  • Elasticsearch学习-- 聚合查询
  • 网课搜题公众号接口
  • ubuntu18.04.1LTS 编译安装ffmpeg详解
  • 接口幂等问题:redis分布式锁解决方案
  • 算法与数据结构(第一周)——线性查找法
  • 修改docker 修改容器配置
  • ARM汇编语言
  • 【通信】非正交多址接入(NOMA)和正交频分多址接入(OFDMA)的性能对比附matlab代码
  • 深入理解控制反转IOC和依赖注入
  • micropython 可视化音频 频谱解析(应该是全网首家)(预告,还没研究完成)
  • 30秒的PHP代码片段(1)数组 - Array
  • Angular 响应式表单 基础例子
  • CSS进阶篇--用CSS开启硬件加速来提高网站性能
  • extract-text-webpack-plugin用法
  • gulp 教程
  • IIS 10 PHP CGI 设置 PHP_INI_SCAN_DIR
  • linux安装openssl、swoole等扩展的具体步骤
  • ng6--错误信息小结(持续更新)
  • Node 版本管理
  • Tornado学习笔记(1)
  • vagrant 添加本地 box 安装 laravel homestead
  • Webpack入门之遇到的那些坑,系列示例Demo
  • 不用申请服务号就可以开发微信支付/支付宝/QQ钱包支付!附:直接可用的代码+demo...
  • 从零开始的webpack生活-0x009:FilesLoader装载文件
  • 观察者模式实现非直接耦合
  • 容器化应用: 在阿里云搭建多节点 Openshift 集群
  • 如何将自己的网站分享到QQ空间,微信,微博等等
  • 跳前端坑前,先看看这个!!
  • 听说你叫Java(二)–Servlet请求
  • 在 Chrome DevTools 中调试 JavaScript 入门
  • (13)Latex:基于ΤΕΧ的自动排版系统——写论文必备
  • (ctrl.obj) : error LNK2038: 检测到“RuntimeLibrary”的不匹配项: 值“MDd_DynamicDebug”不匹配值“
  • (delphi11最新学习资料) Object Pascal 学习笔记---第8章第2节(共同的基类)
  • (DenseNet)Densely Connected Convolutional Networks--Gao Huang
  • (二十四)Flask之flask-session组件
  • (附源码)spring boot火车票售卖系统 毕业设计 211004
  • (转载)PyTorch代码规范最佳实践和样式指南
  • ***通过什么方式***网吧
  • .L0CK3D来袭:如何保护您的数据免受致命攻击
  • .NET 5.0正式发布,有什么功能特性(翻译)
  • .NET Micro Framework初体验
  • /usr/lib/mysql/plugin权限_给数据库增加密码策略遇到的权限问题
  • ::什么意思
  • @SpringBootApplication 包含的三个注解及其含义
  • [ 2222 ]http://e.eqxiu.com/s/wJMf15Ku
  • [ 云计算 | AWS ] AI 编程助手新势力 Amazon CodeWhisperer:优势功能及实用技巧
  • [100天算法】-实现 strStr()(day 52)
  • [C# 基础知识系列]专题十六:Linq介绍
  • [CareerCup] 14.5 Object Reflection 对象反射
  • [codevs 1515]跳 【解题报告】