std :: lock_guard示例,说明其工作原理

时间:2016-02-07 10:04:36

标签: c++ multithreading thread-safety

我已经达到了我的项目中的一个要点,需要在可以写入的资源上的线程之间进行通信,因此同步是必须的。但是,除了基本级别之外,我并不真正理解同步。

考虑此链接中的最后一个示例: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;
}

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

使用两个全局函数:一个由两个编写器线程使用,另一个由读取器线程使用。这两个函数都使用lock_guard来锁定相同的资源列表。

现在这就是我无法解决的问题:读者使用与两个编写器线程不同的范围内的锁,但仍锁定相同的资源。这怎么办?我对互斥体的有限理解非常适合编写器函数,你有两个线程使用完全相同的函数。我可以理解,当你即将进入保护区时,检查是正确的,如果其他人已经在里面,你就等了。

但是当范围不同时?这表明存在某种比进程本身更强大的机制,某种运行时环境阻止了“后期”线程的执行。但我认为c ++中没有这样的东西。所以我不知所措。

这里引人注目的是什么?

3 个答案:

答案 0 :(得分:16)

让我们来看看相关的一行:

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

请注意,lock_guard引用了全局互斥myMutex。也就是说,所有三个线程都使用相同的互斥锁。 lock_guard的作用基本上是这样的:

  • 构建时,它会锁定myMutex并保留对它的引用。
  • 在破坏后(即当守卫的范围被留下时),它会解锁myMutex

互斥锁始终是相同的,它与范围无关。 lock_guard的目的只是为了让您更轻松地锁定和解锁互斥锁。例如,如果您手动lock / unlock,但是您的函数在中间某处抛出异常,它将永远不会到达unlock语句。因此,以的手动方式执行此操作必须确保互斥锁总是解锁。另一方面,只要函数退出,lock_guard对象就会自动销毁 - 无论它是如何退出的。

答案 1 :(得分:13)

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. 不跨多个线程缓存(volatile)。
  2. 读写将是原子操作。
  3. 您的锁可以处理存在多个执行管道(逻辑核心等)的情况。
  4. 有不同的synchronization机制提供“更好的锁定”(跨进程与跨线程,多处理器对比,单处理器等),代价是“性能降低”,因此您应始终选择锁定机制对你的情况来说足够了。

答案 2 :(得分:2)

这正是锁的作用。当一个线程获取锁时,无论代码在何处执行,它都必须等待另一个线程持有锁。当一个线程释放一个锁时,无论它在代码中的哪个位置,它都可以获得该锁。

锁定保护数据,而不是代码。他们通过确保访问受保护数据的所有代码在保持锁定时执行此操作,从任何可能访问相同数据的代码中排除其他线程。