2023年c++学习指南

avatar shenyifan 2023-10-16 21:51 283次浏览 评论已关闭

一、起因今日刷多线程类题目时,遇到最基本的题目:按顺序打印:力扣; 如果你感兴趣,你可以看看主题描述它属于最基本的多线程相互排斥目的是在任何情况下,三个线程必须按顺序1、2、三是执行,顺序不能乱每个人都应该想到解决问题的想法,依次加锁解锁,按线程1、2、三顺序依次解开线程锁即可。

然而,当你阅读解决问题的想法时,你会发现一个大神(ID=**Zhengyu Chen**)以标题的五种方式依次实现,膜拜!特此整理学习一遍

多线程二、题目描述给你一个类:public class Foo {public void first() { print(“first”); }public void second() { print(“second”); }

public void third() { print(“third”); }}三个不同的线程 A、B、C 将共用一个 Foo 实例线程 A 将会调用 first() 方法线程 B 将会调用 second() 方法。

线程 C 将会调用 third() 方法请设计修改程序,以确保 second() 方法在 first() 方法后执行,third() 方法在 second() 方法后执行题目来源:力扣(LeetCode)链接:力扣。

三、解题方案1. 互斥锁互斥锁是一种机制,用于防止多个线程同时访问共享资源对象同时,只有一个线程可以有一个特定的锁对象如果其他线程试图获得锁,它们将被阻塞,直到锁资源被释放或直接返回为了解决这个问题,我们可以用两个互斥锁来阻止它 second 和 third 函数,分别在 first 和 second 执行结束后解锁。

class Foo {mutex mtx1, mtx2;public:Foo() {mtx1.lock(), mtx2.lock();}void first(function printFirst) {

printFirst();mtx1.unlock();}void second(function printSecond) {mtx1.lock();printSecond();mtx1.unlock();

mtx2.unlock();}void third(function printThird) {mtx2.lock();printThird();mtx2.unlock();}};这个代码可以运行,但实际上是这样使用的 mutex 方法是错误的,因为根据 c   在一个线程中尝试标准 mutex 对象进行 unlock 操作时,mutex 对象的所有权必须在这个线程上。

也就是说,一个线程应该由同一个线程对齐 mutex 对象进行 lock 和 unlock 操作,否则会产生未定义的行为标题中提到了 first, second, third 三个函数由三个不同的线程调用,但我们在 Foo 对象结构(可以是 create 这些线程的主线程也可以是三个线程中的任何一个)对两个 mutex 对象进行 lock 因此,操作,调用 first 和 second 至少有一个函数的两个线程试图获得其他线程所拥有的 mutex 对象的所有权。

另外,如果非要讨论这个解法有什么优化的余地,因为 mutex 对象本身并不保护任何数据,我们只是通过它 mutex 保护数据同时访问的机制,因此最好使用 lock_guard 或者 unique_lock 提供的 RAII 机制来管理 mutex 对象,而不是直接操作 mutex 对象;其中 lock_guard 只有结构和分析函数才能实现 RAII 机制,而 unique_lock 是完整的 mutex 所有权包装器包装所有权 mutex 的函数。

lock_guard和unique_lock是RAII机制下的锁,即依靠对象的创建和销毁,即其生命周期自动实现一些逻辑,这两个对象在创建时自动锁定,在销毁时自动解锁因此,如果只依靠对象的生命周期来实现和解锁,它们是相同的,可以使用,因为它们与生命周期有关,所以有时会用花括号来指定它们的生命周期。

因此,如果只依靠对象的生命周期来实现和解锁,它们是相同的,可以使用,因为它们与生命周期有关,所以有时会用花括号来指定它们的生命周期但lock_guard的功能仅限于此unique_lock是lock_guard的扩展,允许lock和unlock在生命周期中被调用来解锁以切换锁。

class Foo {mutex mtx_1, mtx_2;unique_lock lock_1, lock_2;public:Foo() : lock_1(mtx_1, try_to_lock), lock_2(mtx_2, try_to_lock) {

}void first(function printFirst) {printFirst();lock_1.unlock();}void second(function printSecond) {

lock_guard guard(mtx_1);printSecond();lock_2.unlock();}void third(function printThird) {

lock_guard guard(mtx_2);printThird();}};2. 条件变量条件变量一般与互斥锁一起使用,互斥锁用于上锁,条件变量用于在多线程环境中等待特定事件我们可以分别解决这个问题 first 和 second 执行后修改特定变量值(例如修改成员变量) k 为特定值),然后通知条件变量,唤醒下一个函数继续执行。

std::condition_variable 它是一种同步原语,用于同时阻塞多个线程(synchronization primitive)std::condition_variable 必须和 std::unique_lock 搭配使用

class Foo {condition_variable cv;mutex mtx;int k = 0;public:void first(function printFirst) {

printFirst();lock_guard guard(mtx);k = 1;cv.notify_all(); // 在等待唤醒队列中通知所有其他线程}void second(function printSecond)

{unique_lock lock(mtx); // lock mtxcv.wait(lock, [this](){ return k == 1; }); // unlock mtx,并阻止等待唤醒通知,需要满足 k == 1 才能继续运行

printSecond();k = 2;cv.notify_one(); // 随机通知一个(unspecified)等待唤醒队列中的线程}void third(function printThird) {

unique_lock lock(mtx); // lock mtxcv.wait(lock, [this](){ return k == 2; }); // unlock mtx,并阻止等待唤醒通知,需要满足 k == 2 才能继续运行

printThird();}};std::condition_variable::wait 函数将执行三个操作:将当前线程添加到等待唤醒队列中,然后 unlock mutex 对象,最终阻塞当前线程它有两种重载形式,第一种只接收一种 std::mutex 一旦线程接收到唤醒信号(通过。

std::condition_variable::notify_one 或std::condition_variable::notify_all 唤醒),无条件立即被唤醒,并重新 lock mutex;第二种重载形式也会收到一个条件(通常是 variable 或者 std::function),也就是说,只有当满足这一条件时,当前的线程才能被唤醒。

它在 gcc 实现也很简单,只是在第一种重载形式之外加了一个 while 循环确保只有在满足给定条件后才能被唤醒,否则将被重新调用 wait 函数:template

void wait(unique_lock& __lock, _Predicate __p){while (!__p())wait(__lock);}3. 信号量信号量是通过主动等待和主动唤醒来实现共享资源同步访问的机制。

c   我们可以使用标准数据库中没有信号量的实现和包装 c 语言提供的 库来解题 :#include class Foo {private:sem_t sem_1, sem_2;

public:Foo() {sem_init(&sem_1, 0, 0), sem_init(&sem_2, 0, 0);}void first(function printFirst) {

printFirst();sem_post(&sem_1);}void second(function printSecond) {sem_wait(&sem_1);printSecond();

sem_post(&sem_2);}void third(function printThird) {sem_wait(&sem_2);printThird();}};4. 异步操作异步操作是一种让操作继续进行的方法,而无需等待被调用方返回结果。

基于这个问题可以使用 future/promise 异步编程模型future 和 promise 它起源于函数编程,其目的是使值(future)和计算方式(promise)分离,使得 promise 可异步修改 future,从而提高代码的可读性,减少通信延迟。

std::future 模板类用于获取异步操作结果;std::packaged_task, std::promise, std::async 异步操作可以进行,并且有一个 std::future 对象,用于存储其异步操作返回或设置的值(或异常),该值将在未来的某个时间点,通过某种机制修改后,保存在相应的机制中 std::future 对象:对 std::promise,可通过调用 std::promise::set_value 设置值并通知 std::future 对象:。

class Foo {promise pro1, pro2;public:void first(function printFirst) {printFirst();pro1.set_value();

}void second(function printSecond) {pro1.get_future().wait();printSecond();pro2.set_value();}

void third(function printThird) {pro2.get_future().wait();printThird();}};std::future::wait 和 std::future::g

来源:深易凡软件库

发表评论
请先登录后再评论~