与 std::shared_timed_mutex::try_lock_until 的数据竞争
data race with std::shared_timed_mutex::try_lock_until
我正在尝试编写一个小测试用例来练习 std::shared_timed_mutex::try_lock_until
。 cppreference.
上的文档
这是我的代码
#include <thread>
#include <iostream>
#include <chrono>
#include <shared_mutex>
#include <cassert>
std::shared_timed_mutex test_mutex;
int global;
void f()
{
auto now=std::chrono::steady_clock::now();
test_mutex.try_lock_until(now + std::chrono::seconds(100));
//test_mutex.lock();
--global;
std::cout << "In lock, global=" << global << '\n';
test_mutex.unlock();
}
void g()
{
auto now=std::chrono::steady_clock::now();
test_mutex.try_lock_shared_until(now + std::chrono::seconds(10));
//test_mutex.lock_shared();
std::cout << "In shared lock, global=" << global << '\n';
test_mutex.unlock_shared();
}
int main()
{
global = 1;
test_mutex.lock_shared();
std::thread t1(f);
std::thread t2(g);
test_mutex.unlock_shared();
t1.join();
t2.join();
assert(global == 0);
}
我期待的是
- main 获得读锁然后启动 f 和 g
- f 尝试获取独占锁并阻塞
- g 获得读锁,读取
global
然后解锁读锁
- main 解锁读锁
- f 解锁,写入
global
,解锁并完成
- f 和 g 加入
7 断言为真,主要结束
(2 和 3 可以是任意顺序)。
它本身似乎工作正常。在 gdb 下,如果我在 g
中读取 global
并在 f
中写入断点,然后 运行,它会在读取时停止,正如我所期望的那样。
但是,如果我用 -fsanitize=tthread
编译,那么我会遇到危险
WARNING: ThreadSanitizer: data race (pid=6780)
Read of size 4 at 0x000000407298 by thread T2:
#0 g() /home/paulf/scratch/valgrind/drd/tests/try_lock_shared_until14.cpp:25 (try_lock_shared_until14+0x402484)
[trimmed]
#6 execute_native_thread_routine ../../../../../libstdc++-v3/src/c++11/thread.cc:82 (libstdc++.so.6+0xd9c83)
Previous write of size 4 at 0x000000407298 by thread T1:
#0 f() /home/paulf/scratch/valgrind/drd/tests/try_lock_shared_until14.cpp:15 [triimed]
#6 execute_native_thread_routine ../../../../../libstdc++-v3/src/c++11/thread.cc:82 (libstdc++.so.6+0xd9c83)
Location is global 'global' of size 4 at 0x000000407298 (try_lock_shared_until14+0x000000407298)
在 gdb 下,tsan 版本不会阻塞排他锁并首先达到写入。
我意识到我的例子不好,我应该检查 return 值而不是依赖超时。
谁能解释一下 tsan 正在改变什么?如果我使用普通的 lock/lock_shared/unlock/unlock_shared 函数,那么 tsan 就不会再抱怨了。
(请注意,我不能为此使用 DRD 或 Helgrind - 我正在为他们编写测试用例,我知道他们目前不支持此功能,至少在我使用的平台上不支持使用 Fedora 34 / GCC 11.2.1 amd64).
编辑:
这是版本 3,现在可以使用了。 main 等待一个 cv got g()
完成,然后释放共享锁,然后 f()
可以获得独占锁。
#include <thread>
#include <iostream>
#include <chrono>
#include <shared_mutex>
#include <mutex>
#include <cassert>
#include <condition_variable>
std::shared_timed_mutex test_mutex;
std::mutex cv_mutex;
std::condition_variable cv;
int global;
bool reads_done = false;
void f()
{
auto now=std::chrono::steady_clock::now();
std::cout << "In lock, trying to get mutex\n";
if (test_mutex.try_lock_until(now + std::chrono::seconds(3)))
{
--global;
std::cout << "In lock, global=" << global << '\n';
test_mutex.unlock();
}
else
{
std::cerr << "Lock failed\n";
}
}
void g()
{
auto now=std::chrono::steady_clock::now();
std::cout << "In shared lock, trying to get mutex\n";
if (test_mutex.try_lock_shared_until(now + std::chrono::seconds(2)))
{
std::cout << "In shared lock, global=" << global << '\n';
test_mutex.unlock_shared();
}
else
{
std::cerr << "Lock shared failed\n";
}
std::unique_lock<std::mutex> lock(cv_mutex);
reads_done = true;
cv.notify_all();
}
int main()
{
global = 1;
test_mutex.lock_shared();
std::thread t1(f);
std::thread t2(g);
{
std::unique_lock<std::mutex> lock(cv_mutex);
while (!reads_done)
{
cv.wait(lock);
}
}
std::cout << "Main, reader thread done\n";
test_mutex.unlock_shared();
std::cout << "Main, no more shared locks\n";
t1.join();
t2.join();
assert(global == 0);
}
这也是一个有效的调度场景:
- main 获得读锁然后启动 f 和 g
- main 释放读锁然后加入
- f 开始执行并锁定超过 10 毫秒,例如由于抢占
- g 开始执行并阻塞 10 毫秒
- g 解锁并读取共享变量
在 5. 中,发生了数据竞争,因此 ThreadSanitizer 指出它是正确的。更正此错误需要检查 try_lock_shared_until
的 return 值等等。
我正在尝试编写一个小测试用例来练习 std::shared_timed_mutex::try_lock_until
。 cppreference.
这是我的代码
#include <thread>
#include <iostream>
#include <chrono>
#include <shared_mutex>
#include <cassert>
std::shared_timed_mutex test_mutex;
int global;
void f()
{
auto now=std::chrono::steady_clock::now();
test_mutex.try_lock_until(now + std::chrono::seconds(100));
//test_mutex.lock();
--global;
std::cout << "In lock, global=" << global << '\n';
test_mutex.unlock();
}
void g()
{
auto now=std::chrono::steady_clock::now();
test_mutex.try_lock_shared_until(now + std::chrono::seconds(10));
//test_mutex.lock_shared();
std::cout << "In shared lock, global=" << global << '\n';
test_mutex.unlock_shared();
}
int main()
{
global = 1;
test_mutex.lock_shared();
std::thread t1(f);
std::thread t2(g);
test_mutex.unlock_shared();
t1.join();
t2.join();
assert(global == 0);
}
我期待的是
- main 获得读锁然后启动 f 和 g
- f 尝试获取独占锁并阻塞
- g 获得读锁,读取
global
然后解锁读锁 - main 解锁读锁
- f 解锁,写入
global
,解锁并完成 - f 和 g 加入 7 断言为真,主要结束
(2 和 3 可以是任意顺序)。
它本身似乎工作正常。在 gdb 下,如果我在 g
中读取 global
并在 f
中写入断点,然后 运行,它会在读取时停止,正如我所期望的那样。
但是,如果我用 -fsanitize=tthread
编译,那么我会遇到危险
WARNING: ThreadSanitizer: data race (pid=6780)
Read of size 4 at 0x000000407298 by thread T2:
#0 g() /home/paulf/scratch/valgrind/drd/tests/try_lock_shared_until14.cpp:25 (try_lock_shared_until14+0x402484)
[trimmed]
#6 execute_native_thread_routine ../../../../../libstdc++-v3/src/c++11/thread.cc:82 (libstdc++.so.6+0xd9c83)
Previous write of size 4 at 0x000000407298 by thread T1:
#0 f() /home/paulf/scratch/valgrind/drd/tests/try_lock_shared_until14.cpp:15 [triimed]
#6 execute_native_thread_routine ../../../../../libstdc++-v3/src/c++11/thread.cc:82 (libstdc++.so.6+0xd9c83)
Location is global 'global' of size 4 at 0x000000407298 (try_lock_shared_until14+0x000000407298)
在 gdb 下,tsan 版本不会阻塞排他锁并首先达到写入。
我意识到我的例子不好,我应该检查 return 值而不是依赖超时。
谁能解释一下 tsan 正在改变什么?如果我使用普通的 lock/lock_shared/unlock/unlock_shared 函数,那么 tsan 就不会再抱怨了。
(请注意,我不能为此使用 DRD 或 Helgrind - 我正在为他们编写测试用例,我知道他们目前不支持此功能,至少在我使用的平台上不支持使用 Fedora 34 / GCC 11.2.1 amd64).
编辑:
这是版本 3,现在可以使用了。 main 等待一个 cv got g()
完成,然后释放共享锁,然后 f()
可以获得独占锁。
#include <thread>
#include <iostream>
#include <chrono>
#include <shared_mutex>
#include <mutex>
#include <cassert>
#include <condition_variable>
std::shared_timed_mutex test_mutex;
std::mutex cv_mutex;
std::condition_variable cv;
int global;
bool reads_done = false;
void f()
{
auto now=std::chrono::steady_clock::now();
std::cout << "In lock, trying to get mutex\n";
if (test_mutex.try_lock_until(now + std::chrono::seconds(3)))
{
--global;
std::cout << "In lock, global=" << global << '\n';
test_mutex.unlock();
}
else
{
std::cerr << "Lock failed\n";
}
}
void g()
{
auto now=std::chrono::steady_clock::now();
std::cout << "In shared lock, trying to get mutex\n";
if (test_mutex.try_lock_shared_until(now + std::chrono::seconds(2)))
{
std::cout << "In shared lock, global=" << global << '\n';
test_mutex.unlock_shared();
}
else
{
std::cerr << "Lock shared failed\n";
}
std::unique_lock<std::mutex> lock(cv_mutex);
reads_done = true;
cv.notify_all();
}
int main()
{
global = 1;
test_mutex.lock_shared();
std::thread t1(f);
std::thread t2(g);
{
std::unique_lock<std::mutex> lock(cv_mutex);
while (!reads_done)
{
cv.wait(lock);
}
}
std::cout << "Main, reader thread done\n";
test_mutex.unlock_shared();
std::cout << "Main, no more shared locks\n";
t1.join();
t2.join();
assert(global == 0);
}
这也是一个有效的调度场景:
- main 获得读锁然后启动 f 和 g
- main 释放读锁然后加入
- f 开始执行并锁定超过 10 毫秒,例如由于抢占
- g 开始执行并阻塞 10 毫秒
- g 解锁并读取共享变量
在 5. 中,发生了数据竞争,因此 ThreadSanitizer 指出它是正确的。更正此错误需要检查 try_lock_shared_until
的 return 值等等。