为什么互斥锁在没有锁卫的情况下不起作用?
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_var
在 task_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);
,得到预期结果
问题:
- 我目前的方法有什么问题?
- 为什么它适用于 loack_guard?
互斥锁不锁定变量,它只是锁定互斥锁,这样其他代码就不能同时锁定同一个互斥锁。
换句话说,所有对共享变量的访问都需要包装在同一个互斥锁上的互斥锁中,以避免对同一个变量的多个同时访问,它不是自动的,因为变量被包装在代码中另一个地方的互斥锁。
您根本没有在任务 2 中锁定互斥量,因此存在竞争条件。
当您将互斥量包装在 std::lock_guard 中时它似乎起作用的原因是锁守卫持有互斥量锁直到范围结束,在这种情况下是函数的结束。
您的函数首先使用锁 lock_guard 锁定互斥锁,稍后在同一范围内尝试使用解锁 lock_guard 锁定同一个互斥锁。由于互斥量已经被锁 lock_guard 锁定,执行停止并且没有输出,因为程序实际上不再是 运行。
如果您在代码中的“//do something”注释处输出“ok”,您会看到您获得了一次输出,然后程序停止了所有输出。
注意;由于这种行为得到保证,请参阅@Jarod42s 答案以获得更好的信息。与 C++ 中大多数意外行为一样,可能涉及 UB。
使用 UB(作为数据竞争),输出未确定,您可能会看到 “预期” 输出,或奇怪的东西,崩溃,...
- What is the problem in my current approach?
在第一个示例中,在没有同步的情况下在一个线程中写入 (non-atomic) shared_var
并在另一个线程中读取时存在数据竞争。
- Why does it work with loack_guard?
在修改后的示例中,您锁定了两次相同的 (non-recursive) 互斥量,这也是 UB
If lock is called by a thread that already owns the mutex, the behavior is undefined
对于 2 个不同的 UB,你只有 2 个不同的行为(当两种情况都可能发生任何事情时)。
假设您的房间有两个条目。一个入口有门,另一个没有。这个房间叫做shared_var
。有两个家伙想进房间,他们叫task_1
和task_2
。
您现在想以某种方式确保任何时候房间内只有其中一个人。
taks_2
可以通过无门的入口自由进入房间。 task_1
使用名为 shared_mutex
的门。
你现在的问题是:在第一次进门的门上加一把锁,能不能实现只有一个人在房间里?
显然不是,因为第二扇门仍然可以进出,而你无法控制它。
如果您进行实验,您可能会发现,如果没有锁,您会在房间里找到两个人,而在添加锁后,您不会在房间里找到两个人。虽然这纯粹是运气(实际上是运气不好,因为它让你相信锁有用)。实际上锁并没有太大变化。当另一个人在房间里时,叫 task_2
的人仍然可以进入房间。
解决办法是让两者通过同一扇门。他们进屋时锁上门,离开房间时解锁。给门装上自动锁也不错,因为这样他们离开时就不会忘记打开门了。
哦,对不起,我讲故事的时候迷路了。
TL;DR:在您的代码中,是否使用锁并不重要。实际上你的代码中的互斥量也是无用的,因为只有一个线程un/locks它。要正确使用互斥量,两个线程都需要在 reading/writing 共享内存之前锁定它。
我有以下代码:
#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_var
在 task_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);
问题:
- 我目前的方法有什么问题?
- 为什么它适用于 loack_guard?
互斥锁不锁定变量,它只是锁定互斥锁,这样其他代码就不能同时锁定同一个互斥锁。
换句话说,所有对共享变量的访问都需要包装在同一个互斥锁上的互斥锁中,以避免对同一个变量的多个同时访问,它不是自动的,因为变量被包装在代码中另一个地方的互斥锁。
您根本没有在任务 2 中锁定互斥量,因此存在竞争条件。
当您将互斥量包装在 std::lock_guard 中时它似乎起作用的原因是锁守卫持有互斥量锁直到范围结束,在这种情况下是函数的结束。
您的函数首先使用锁 lock_guard 锁定互斥锁,稍后在同一范围内尝试使用解锁 lock_guard 锁定同一个互斥锁。由于互斥量已经被锁 lock_guard 锁定,执行停止并且没有输出,因为程序实际上不再是 运行。
如果您在代码中的“//do something”注释处输出“ok”,您会看到您获得了一次输出,然后程序停止了所有输出。
注意;由于这种行为得到保证,请参阅@Jarod42s 答案以获得更好的信息。与 C++ 中大多数意外行为一样,可能涉及 UB。
使用 UB(作为数据竞争),输出未确定,您可能会看到 “预期” 输出,或奇怪的东西,崩溃,...
- What is the problem in my current approach?
在第一个示例中,在没有同步的情况下在一个线程中写入 (non-atomic) shared_var
并在另一个线程中读取时存在数据竞争。
- Why does it work with loack_guard?
在修改后的示例中,您锁定了两次相同的 (non-recursive) 互斥量,这也是 UB
If lock is called by a thread that already owns the mutex, the behavior is undefined
对于 2 个不同的 UB,你只有 2 个不同的行为(当两种情况都可能发生任何事情时)。
假设您的房间有两个条目。一个入口有门,另一个没有。这个房间叫做shared_var
。有两个家伙想进房间,他们叫task_1
和task_2
。
您现在想以某种方式确保任何时候房间内只有其中一个人。
taks_2
可以通过无门的入口自由进入房间。 task_1
使用名为 shared_mutex
的门。
你现在的问题是:在第一次进门的门上加一把锁,能不能实现只有一个人在房间里?
显然不是,因为第二扇门仍然可以进出,而你无法控制它。
如果您进行实验,您可能会发现,如果没有锁,您会在房间里找到两个人,而在添加锁后,您不会在房间里找到两个人。虽然这纯粹是运气(实际上是运气不好,因为它让你相信锁有用)。实际上锁并没有太大变化。当另一个人在房间里时,叫 task_2
的人仍然可以进入房间。
解决办法是让两者通过同一扇门。他们进屋时锁上门,离开房间时解锁。给门装上自动锁也不错,因为这样他们离开时就不会忘记打开门了。
哦,对不起,我讲故事的时候迷路了。
TL;DR:在您的代码中,是否使用锁并不重要。实际上你的代码中的互斥量也是无用的,因为只有一个线程un/locks它。要正确使用互斥量,两个线程都需要在 reading/writing 共享内存之前锁定它。