当前位置: 首页 > 知识库问答 >
问题:

std::lock_guard示例,解释其工作原理

慕容嘉荣
2023-03-14

在我的项目中,我已经达到了一个点,需要在资源上的线程之间进行通信,这些资源很可能被写入,所以同步是必须的。然而,除了基本级别,我真的不理解同步。

考虑这个链接中的最后一个例子:http://www.bogotobogo.com/cplusplus/C11/7_C11_Thread_Sharing_Memory.php

#include <iostream>
#include <thread>
#include <list>
#include <algorithm>
#include <mutex>

using namespace std;

// a global variable
std::list<int>myList;

// a global instance of std::mutex to protect global variable
std::mutex myMutex;

void addToList(int max, int interval)
{
    // the access to this function is mutually exclusive
    std::lock_guard<std::mutex> guard(myMutex);
    for (int i = 0; i < max; i++) {
        if( (i % interval) == 0) myList.push_back(i);
    }
}

void printList()
{
    // the access to this function is mutually exclusive
    std::lock_guard<std::mutex> guard(myMutex);
    for (auto itr = myList.begin(), end_itr = myList.end(); itr != end_itr; ++itr ) {
        cout << *itr << ",";
    }
}

int main()
{
    int max = 100;

    std::thread t1(addToList, max, 1);
    std::thread t2(addToList, max, 10);
    std::thread t3(printList);

    t1.join();
    t2.join();
    t3.join();

    return 0;
}

该示例演示了三个线程(两个编写器和一个读取器)如何访问公共资源(列表)。

使用两个全局函数:一个由两个writer线程使用,另一个由reader线程使用。这两个函数都使用一个锁定保护来锁定同一个资源,即列表。

下面是我无法理解的:读卡器在不同于两个writer线程的范围内使用锁,但仍然锁定相同的资源。这怎么行?我对互斥体的有限理解很适合writer函数,这里有两个线程使用完全相同的函数。我可以理解,当你即将进入保护区时,检查是正确的,如果其他人已经在里面,你就等着。

但是当范围不同的时候呢?这将表明有某种机制进程本身更强大,某种运行时环境阻止了“后期”线程的执行。但是我以为c里没有这些东西。所以我很茫然。

引擎盖下面到底发生了什么?

共有3个答案

卜泓
2023-03-14

只是为了补充其他人所说的...

在C语言中有一个叫做资源获取是初始化(RAII)的想法,即将资源绑定到对象的生命周期:

资源获取是初始化或RAII,是一种C编程技术,它绑定了使用前必须获取的资源的生命周期(分配的堆内存、执行线程、打开套接字、打开文件、锁定互斥锁、磁盘空间、数据库连接-任何存在于有限供应)到对象的生命周期。

C RAII信息

使用std::lock_-guard

这为什么有用?

考虑一个不使用<代码> STD::LoCyGueGue<代码>的情况:

std::mutex m; // global mutex
void oops() {
   m.lock();
   doSomething();
   m.unlock();
}

在这种情况下,使用全局互斥锁,并在调用do某物()之前锁定。然后一旦do某物()完成,互斥锁就会解锁。

这里的一个问题是,如果出现异常会发生什么?现在,您可能永远无法到达m.unlock()行,该行将互斥锁释放到其他线程。因此,您需要涵盖遇到异常的情况:

std::mutex m; // global mutex
void oops() {
   try {
      m.lock();
      doSomething();
      m.unlock();
   } catch(...) {
      m.unlock(); // now exception path is covered
      // throw ...
   }
}

这工作,但丑陋,冗长,不方便。

现在,让我们编写自己的简单锁保护。

class lock_guard {
private:
   std::mutex& m;
public: 
   lock_guard(std::mutex& m_):(m(m_)){ m.lock(); }  // lock on construction
   ~lock_guard() { t.unlock(); }}                   // unlock on deconstruction
}

当lock_guard对象被销毁时,它将确保互斥锁被解锁。现在,我们可以使用这个锁紧装置以更好/更干净的方式处理以前的箱子:

std::mutex m; // global mutex
void ok() {
      lock_guard lk(m); // our simple lock guard, protects against exception case 
      doSomething(); 
} // when scope is exited our lock guard object is destroyed and the mutex unlocked

这与std::lock_guard背后的想法相同。

同样,这种方法也适用于许多不同类型的资源,您可以通过RAII上的链接了解更多信息。

麹浩瀚
2023-03-14

myMutex是全局的,这是用来保护myListguard(myMutex)只需打开锁,从块中退出会导致其破坏,从而解除锁guard是一种方便的锁合和解锁方式。

这样一来,mutex就不能保护任何数据。它只是提供了一种保护数据的方法。保护数据的是设计模式。因此,如果我编写自己的函数来修改列表,如下所示,mutex无法保护它。

void addToListUnsafe(int max, int interval)
{
    for (int i = 0; i < max; i++) {
        if( (i % interval) == 0) myList.push_back(i);
    }
}

只有当需要访问数据的所有代码在访问前都已锁定,并在访问完成后解除锁定时,锁才起作用。这种在每次访问之前和之后打开和关闭锁的设计模式是保护数据的方式(myList

现在你会想,为什么要使用mutex,为什么不使用bool。是的,您可以,但您必须确保bool变量将显示某些特征,包括但不限于以下列表。

  1. 不能跨多个线程缓存(易失性)
  2. 读写将是原子操作
  3. 锁可以处理存在多个执行管道(逻辑核等)的情况

有不同的同步机制可以提供“更好的锁定”(跨进程与跨线程、多处理器与单处理器等),但代价是“更慢的性能”,因此您应该始终选择一种适合您的情况的锁定机制。

上官联
2023-03-14

让我们看看相关的行:

std::lock_guard<std::mutex> guard(myMutex);

请注意,lock\u-guard引用全局互斥体myMutex。也就是说,所有三个线程都使用相同的互斥锁。lock_guard的基本功能是:

  • 在构造时,它锁定myMutex并保留对它的引用
  • 销毁后(即当警卫的作用域离开时),它会解锁myMutex

互斥锁总是同一个,它与作用域无关。lock_guard的目的就是让你更容易锁定和解锁互斥锁。例如,如果您手动<代码>锁定<代码> >代码>解锁< /代码>,但是您的函数在中间抛出一个异常,它将永远不会达到<代码>解锁< /代码>语句。因此,手动操作时,必须确保互斥锁始终处于解锁状态。另一方面,每当函数退出时,lock\u guard对象就会自动被销毁,无论它是如何退出的。

 类似资料:
  • 我有两个用例。 a.我想为两个线程同步对队列的访问。

  • 问题内容: 这是python装饰器的示例。我无法理解它的工作方式。请向我解释给定示例的控制流程。我将非常有义务。 问题答案: 装饰器是在Python中应用高阶函数的语法糖。高阶函数是将一个或多个函数作为输入并返回一个函数的函数。即 这里是一个高阶函数,它接受单个参数的函数,并返回单个参数的函数。您可以将其视为修改的行为。 高阶函数是可组合的(根据定义),因此在您的特定示例中,装饰器语法, 等价于

  • 我克隆了存储库:https://github.com/facebook/react-native,并尝试在Android仿真器上运行UIExplorer示例。 我在Android模拟器上收到了这个错误消息:

  • 问题内容: 我正在尝试了解其工作原理。 我在他们的文档中看到他们使用预先填充的用户列表。我想玩一个数据库存储的用户列表。 但是,我不了解本模块中的某些内容。 每次请求都会调用此代码吗?这是用来加载我的用户对象的所有详细信息吗? 现在,我有以下代码: 当我访问/ make-login时,我想登录。 我的用户类别: 另外,我写了另外两个用于身份验证/注册的功能 我不知道如何使它与MySQL一起使用。另

  • 问题内容: 我得到以下代码: 我可以理解诸如阶乘和斐波那契这样的递归,但是对于这一点我不能理解。我试图追踪逻辑: 我总是以其他任何数字结尾7,我知道这是错误的,因为在运行程序时会得到不同的值。您能帮我了解递归在这里如何工作吗? 问题答案: 我认为这是不言自明的,如果您需要更多信息,请评论!

  • 本文向大家介绍java中Servlet监听器的工作原理及示例详解,包括了java中Servlet监听器的工作原理及示例详解的使用技巧和注意事项,需要的朋友参考一下 监听器就是一个实现特定接口的普通java程序,这个程序专门用于监听另一个java对象的方法调用或属性改变,当被监听对象发生上述事件后,监听器某个方法将立即被执行。 监听器原理 监听原理 1、存在事件源 2、提供监听器 3、为事件源注册监