为什么互斥锁在没有锁卫的情况下不起作用?

Why doesn't mutex work without lock guard?

我有以下代码:

#include <chrono>
#include <iostream>
#include <mutex>
#include <thread>

int shared_var {0};
std::mutex shared_mutex;

void task_1()
{
    while (true)
    {
        shared_mutex.lock();
        const auto temp = shared_var;
        std::this_thread::sleep_for(std::chrono::seconds(1));
        
        if(temp == shared_var)
        {
            //do something
        }
        else
        {
            const auto timenow = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
            std::cout << ctime(&timenow) << ": Data race at task_1: shared resource corrupt \n";
            std::cout << "Actual value: " << shared_var << "Expected value: " << temp << "\n"; 
        }
        shared_mutex.unlock();
    }
}


void task_2()
{
    while (true)
    {
        std::this_thread::sleep_for(std::chrono::seconds(2));
        ++shared_var;
    
    }
}

int main()
{
    auto task_1_thread = std::thread(task_1);
    auto task_2_thread = std::thread(task_2);
 
    task_1_thread.join();
    task_2_thread.join();
    return 0;
}

shared_vartask_1 中受保护但在 task_2 中不受保护 什么是预期的: 我原以为 else 分支没有进入 task_1 因为共享资源被锁定。 实际发生了什么: 运行 此代码将进入 task_1 中的 else 分支。

shared_mutex.lock();替换为std::lock_guard<std::mutex> lock(shared_mutex);,将shared_mutex.unlock();替换为std::lock_guard<std::mutex> unlock(shared_mutex);

,得到预期结果

问题:

  1. 我目前的方法有什么问题?
  2. 为什么它适用于 loack_guard?

我是运行代码: https://www.onlinegdb.com/online_c++_compiler

互斥锁不锁定变量,它只是锁定互斥锁,这样其他代码就不能同时锁定同一个互斥锁。

换句话说,所有对共享变量的访问都需要包装在同一个互斥锁上的互斥锁中,以避免对同一个变量的多个同时访问,它不是自动的,因为变量被包装在代码中另一个地方的互斥锁。

您根本没有在任务 2 中锁定互斥量,因此存在竞争条件。

当您将互斥量包装在 std::lock_guard 中时它似乎起作用的原因是锁守卫持有互斥量锁直到范围结束,在这种情况下是函数的结束。

您的函数首先使用锁 lock_guard 锁定互斥锁,稍后在同一范围内尝试使用解锁 lock_guard 锁定同一个互斥锁。由于互斥量已经被锁 lock_guard 锁定,执行停止并且没有输出,因为程序实际上不再是 运行。

如果您在代码中的“//do something”注释处输出“ok”,您会看到您获得了一次输出,然后程序停止了所有输出。

注意;由于这种行为得到保证,请参阅@Jarod42s 答案以获得更好的信息。与 C++ 中大多数意外行为一样,可能涉及 UB。

使用 UB(作为数据竞争),输出未确定,您可能会看到 “预期” 输出,或奇怪的东西,崩溃,...

  1. What is the problem in my current approach?

在第一个示例中,在没有同步的情况下在一个线程中写入 (non-atomic) shared_var 并在另一个线程中读取时存在数据竞争。

  1. Why does it work with loack_guard?

在修改后的示例中,您锁定了两次相同的 (non-recursive) 互斥量,这也是 UB

来自std::mutex::lock

If lock is called by a thread that already owns the mutex, the behavior is undefined

对于 2 个不同的 UB,你只有 2 个不同的行为(当两种情况都可能发生任何事情时)。

假设您的房间有两个条目。一个入口有门,另一个没有。这个房间叫做shared_var。有两个家伙想进房间,他们叫task_1task_2

您现在想以某种方式确保任何时候房间内只有其中一个人。

taks_2可以通过无门的入口自由进入房间。 task_1 使用名为 shared_mutex 的门。

你现在的问题是:在第一次进门的门上加一把锁,能不能实现只有一个人在房间里?

显然不是,因为第二扇门仍然可以进出,而你无法控制它。

如果您进行实验,您可能会发现,如果没有锁,您会在房间里找到两个人,而在添加锁后,您不会在房间里找到两个人。虽然这纯粹是运气(实际上是运气不好,因为它让你相信锁有用)。实际上锁并没有太大变化。当另一个人在房间里时,叫 task_2 的人仍然可以进入房间。

解决办法是让两者通过同一扇门。他们进屋时锁上门,离开房间时解锁。给门装上自动锁也不错,因为这样他们离开时就不会忘记打开门了。

哦,对不起,我讲故事的时候迷路了。

TL;DR:在您的代码中,是否使用锁并不重要。实际上你的代码中的互斥量也是无用的,因为只有一个线程un/locks它。要正确使用互斥量,两个线程都需要在 reading/writing 共享内存之前锁定它。