对于验证代码是否是线程安全,往往是十分困难的,有一些工具可以帮我们简化这项任务,以尽可能保证并发的正确性。
ThreadSanitizer
谷歌出品,内置于编译器的一种线程安全分析工具,使用方法就是在编译时加上-fsanitize=thread 配置项即可。
现在来简单尝试分析如下代码:
#include <thread>
#include <iostream>
bool flag = false;
void producer() {
printf("profucer\n");
flag = true;
}
void consumer() {
while(!flag);
printf("consumer\n");
}
int main() {
std::thread t1(producer);
std::thread t2(consumer);
t1.join();
t2.join();
printf("\n");
}
如果你使用gcc -g 模式编译上述代码,并执行,你大概率会得到正确的输出顺序。
profucer
consumer
profucer
consumer
profucer
consumer
profucer
consumer
但是这是一个经典的线程不安全代码, 现在我们来加上配置项,分析一下线程不安全的原因。
==================
WARNING: ThreadSanitizer: data race (pid=19240)
Read of size 1 at 0xaaaaaaab4020 by thread T2:
#0 consumer() /home/taskflow/demo/simple.cpp:19 (simple+0x1258)
#1 void std::__invoke_impl<void, void (*)()>(std::__invoke_other, void (*&&)()) /usr/include/c++/10/bits/invoke.h:60 (simple+0x2238)
#2 std::__invoke_result<void (*)()>::type std::__invoke<void (*)()>(void (*&&)()) /usr/include/c++/10/bits/invoke.h:95 (simple+0x215c)
#3 void std::thread::_Invoker<std::tuple<void (*)()> >::_M_invoke<0ul>(std::_Index_tuple<0ul>) /usr/include/c++/10/thread:264 (simple+0x209c)
#4 std::thread::_Invoker<std::tuple<void (*)()> >::operator()() /usr/include/c++/10/thread:271 (simple+0x203c)
#5 std::thread::_State_impl<std::thread::_Invoker<std::tuple<void (*)()> > >::_M_run() /usr/include/c++/10/thread:215 (simple+0x1fe8)
#6 <null> <null> (libstdc++.so.6+0xccf98)
Previous write of size 1 at 0xaaaaaaab4020 by thread T1:
#0 producer() /home/taskflow/demo/simple.cpp:16 (simple+0x11f8)
#1 void std::__invoke_impl<void, void (*)()>(std::__invoke_other, void (*&&)()) /usr/include/c++/10/bits/invoke.h:60 (simple+0x2238)
#2 std::__invoke_result<void (*)()>::type std::__invoke<void (*)()>(void (*&&)()) /usr/include/c++/10/bits/invoke.h:95 (simple+0x215c)
#3 void std::thread::_Invoker<std::tuple<void (*)()> >::_M_invoke<0ul>(std::_Index_tuple<0ul>) /usr/include/c++/10/thread:264 (simple+0x209c)
#4 std::thread::_Invoker<std::tuple<void (*)()> >::operator()() /usr/include/c++/10/thread:271 (simple+0x203c)
#5 std::thread::_State_impl<std::thread::_Invoker<std::tuple<void (*)()> > >::_M_run() /usr/include/c++/10/thread:215 (simple+0x1fe8)
#6 <null> <null> (libstdc++.so.6+0xccf98)
Location is global 'flag' of size 1 at 0xaaaaaaab4020 (simple+0x000000014020)
Thread T2 (tid=19243, running) created by main thread at:
#0 pthread_create ../../../../src/libsanitizer/tsan/tsan_interceptors_posix.cpp:962 (libtsan.so.0+0x61880)
#1 std::thread::_M_start_thread(std::unique_ptr<std::thread::_State, std::default_delete<std::thread::_State> >, void (*)()) <null> (libstdc++.so.6+0xcd2bc)
#2 main /home/taskflow/demo/simple.cpp:25 (simple+0x12fc)
Thread T1 (tid=19242, finished) created by main thread at:
#0 pthread_create ../../../../src/libsanitizer/tsan/tsan_interceptors_posix.cpp:962 (libtsan.so.0+0x61880)
#1 std::thread::_M_start_thread(std::unique_ptr<std::thread::_State, std::default_delete<std::thread::_State> >, void (*)()) <null> (libstdc++.so.6+0xcd2bc)
#2 main /home/taskflow/demo/simple.cpp:24 (simple+0x12e8)
SUMMARY: ThreadSanitizer: data race /home/taskflow/demo/simple.cpp:19 in consumer()
==================
由上述的异常日志看出,设计数据竞争的部分(临界区),是全局变量flag。
然后是经典的生产消费模型:
template<typename T>
class Queue {
private:
T* m_queue;
std::size_t front, back; // front指向下一个要push的位置,back指向下一个要pop的位置
static constexpr std::size_t Maxm = 1024;
public:
Queue(): m_queue{new T[Maxm]}, front{0}, back{0} {} // 先不考虑扩容的事情
~Queue() {delete[] m_queue;}
// 先不考虑扩容问题
void push(const T& item) {
m_queue[front] = item;
front++; // 先不考虑容量,保证每次push次数少于Maxm
}
T pop() {
while(back >= front) {
}
T item = m_queue[back];
++back;
return item;
}
};
void Producer(Queue<int>& queue, int num_items) {
for (int i = 0; i < num_items; ++i) {
queue.push(i);
}
}
void Consumer(Queue<int>& queue, int num_items) {
int cur = 0;
for (int i = 0; i < num_items; ++i) {
int item = queue.pop(); // 阻塞式等待
// std::cout << "Consumed: " << item << std::endl;
assert(cur == item);
cur++;
}
std::cout<<"done\n";
}
void TestSingleProducerSingleConsumer() {
Queue<int> queue;
const int num_items = 1000;
std::thread consumer_thread(Consumer, std::ref(queue), num_items);
std::thread producer_thread(Producer, std::ref(queue), num_items);
producer_thread.join();
consumer_thread.join();
}
int main() {
int n = 1;
auto start = std::chrono::high_resolution_clock::now();
while(n--) {
TestSingleProducerSingleConsumer();
}
auto end = std::chrono::high_resolution_clock::now();
std::cout << "Time taken: " << std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count() << " ms" << std::endl;
}
==================
WARNING: ThreadSanitizer: data race (pid=42564)
Write of size 8 at 0xfffffffff248 by thread T2:
#0 Queue<int>::push(int const&) /home/taskflow/demo/banckmark.cpp:28 (banckmark+0x1c08)
#1 Producer(Queue<int>&, int) /home/taskflow/demo/banckmark.cpp:48 (banckmark+0x14d0)
#2 void std::__invoke_impl<void, void (*)(Queue<int>&, int), std::reference_wrapper<Queue<int> >, int>(std::__invoke_other, void (*&&)(Queue<int>&, int), std::reference_wrapper<Queue<int> >&&, int&&) /usr/include/c++/10/bits/invoke.h:60 (banckmark+0x33d4)
#3 std::__invoke_result<void (*)(Queue<int>&, int), std::reference_wrapper<Queue<int> >, int>::type std::__invoke<void (*)(Queue<int>&, int), std::reference_wrapper<Queue<int> >, int>(void (*&&)(Queue<int>&, int), std::reference_wrapper<Queue<int> >&&, int&&) /usr/include/c++/10/bits/invoke.h:95 (banckmark+0x31ec)
#4 void std::thread::_Invoker<std::tuple<void (*)(Queue<int>&, int), std::reference_wrapper<Queue<int> >, int> >::_M_invoke<0ul, 1ul, 2ul>(std::_Index_tuple<0ul, 1ul, 2ul>) /usr/include/c++/10/thread:264 (banckmark+0x3068)
#5 std::thread::_Invoker<std::tuple<void (*)(Queue<int>&, int), std::reference_wrapper<Queue<int> >, int> >::operator()() /usr/include/c++/10/thread:271 (banckmark+0x2fdc)
#6 std::thread::_State_impl<std::thread::_Invoker<std::tuple<void (*)(Queue<int>&, int), std::reference_wrapper<Queue<int> >, int> > >::_M_run() /usr/include/c++/10/thread:215 (banckmark+0x2f88)
#7 <null> <null> (libstdc++.so.6+0xccf98)
Previous read of size 8 at 0xfffffffff248 by thread T1:
#0 Queue<int>::pop() /home/taskflow/demo/banckmark.cpp:32 (banckmark+0x1c68)
#1 Consumer(Queue<int>&, int) /home/taskflow/demo/banckmark.cpp:55 (banckmark+0x156c)
#2 void std::__invoke_impl<void, void (*)(Queue<int>&, int), std::reference_wrapper<Queue<int> >, int>(std::__invoke_other, void (*&&)(Queue<int>&, int), std::reference_wrapper<Queue<int> >&&, int&&) /usr/include/c++/10/bits/invoke.h:60 (banckmark+0x33d4)
#3 std::__invoke_result<void (*)(Queue<int>&, int), std::reference_wrapper<Queue<int> >, int>::type std::__invoke<void (*)(Queue<int>&, int), std::reference_wrapper<Queue<int> >, int>(void (*&&)(Queue<int>&, int), std::reference_wrapper<Queue<int> >&&, int&&) /usr/include/c++/10/bits/invoke.h:95 (banckmark+0x31ec)
#4 void std::thread::_Invoker<std::tuple<void (*)(Queue<int>&, int), std::reference_wrapper<Queue<int> >, int> >::_M_invoke<0ul, 1ul, 2ul>(std::_Index_tuple<0ul, 1ul, 2ul>) /usr/include/c++/10/thread:264 (banckmark+0x3068)
#5 std::thread::_Invoker<std::tuple<void (*)(Queue<int>&, int), std::reference_wrapper<Queue<int> >, int> >::operator()() /usr/include/c++/10/thread:271 (banckmark+0x2fdc)
#6 std::thread::_State_impl<std::thread::_Invoker<std::tuple<void (*)(Queue<int>&, int), std::reference_wrapper<Queue<int> >, int> > >::_M_run() /usr/include/c++/10/thread:215 (banckmark+0x2f88)
#7 <null> <null> (libstdc++.so.6+0xccf98)
Location is stack of main thread.
Location is global '<null>' at 0x000000000000 ([stack]+0x000000020248)
Thread T2 (tid=42567, running) created by main thread at:
#0 pthread_create ../../../../src/libsanitizer/tsan/tsan_interceptors_posix.cpp:962 (libtsan.so.0+0x61880)
#1 std::thread::_M_start_thread(std::unique_ptr<std::thread::_State, std::default_delete<std::thread::_State> >, void (*)()) <null> (libstdc++.so.6+0xcd2bc)
#2 TestSingleProducerSingleConsumer() /home/taskflow/demo/banckmark.cpp:67 (banckmark+0x1698)
#3 main /home/taskflow/demo/banckmark.cpp:80 (banckmark+0x17a0)
Thread T1 (tid=42566, running) created by main thread at:
#0 pthread_create ../../../../src/libsanitizer/tsan/tsan_interceptors_posix.cpp:962 (libtsan.so.0+0x61880)
#1 std::thread::_M_start_thread(std::unique_ptr<std::thread::_State, std::default_delete<std::thread::_State> >, void (*)()) <null> (libstdc++.so.6+0xcd2bc)
#2 TestSingleProducerSingleConsumer() /home/taskflow/demo/banckmark.cpp:66 (banckmark+0x1668)
#3 main /home/taskflow/demo/banckmark.cpp:80 (banckmark+0x17a0)
SUMMARY: ThreadSanitizer: data race /home/taskflow/demo/banckmark.cpp:28 in Queue<int>::push(int const&)
==================
==================
WARNING: ThreadSanitizer: data race (pid=42564)
Read of size 4 at 0xfffff4103000 by thread T1:
#0 Queue<int>::pop() /home/taskflow/demo/banckmark.cpp:37 (banckmark+0x1cb0)
#1 Consumer(Queue<int>&, int) /home/taskflow/demo/banckmark.cpp:55 (banckmark+0x156c)
#2 void std::__invoke_impl<void, void (*)(Queue<int>&, int), std::reference_wrapper<Queue<int> >, int>(std::__invoke_other, void (*&&)(Queue<int>&, int), std::reference_wrapper<Queue<int> >&&, int&&) /usr/include/c++/10/bits/invoke.h:60 (banckmark+0x33d4)
#3 std::__invoke_result<void (*)(Queue<int>&, int), std::reference_wrapper<Queue<int> >, int>::type std::__invoke<void (*)(Queue<int>&, int), std::reference_wrapper<Queue<int> >, int>(void (*&&)(Queue<int>&, int), std::reference_wrapper<Queue<int> >&&, int&&) /usr/include/c++/10/bits/invoke.h:95 (banckmark+0x31ec)
#4 void std::thread::_Invoker<std::tuple<void (*)(Queue<int>&, int), std::reference_wrapper<Queue<int> >, int> >::_M_invoke<0ul, 1ul, 2ul>(std::_Index_tuple<0ul, 1ul, 2ul>) /usr/include/c++/10/thread:264 (banckmark+0x3068)
#5 std::thread::_Invoker<std::tuple<void (*)(Queue<int>&, int), std::reference_wrapper<Queue<int> >, int> >::operator()() /usr/include/c++/10/thread:271 (banckmark+0x2fdc)
#6 std::thread::_State_impl<std::thread::_Invoker<std::tuple<void (*)(Queue<int>&, int), std::reference_wrapper<Queue<int> >, int> > >::_M_run() /usr/include/c++/10/thread:215 (banckmark+0x2f88)
#7 <null> <null> (libstdc++.so.6+0xccf98)
Previous write of size 4 at 0xfffff4103000 by thread T2:
#0 Queue<int>::push(int const&) /home/taskflow/demo/banckmark.cpp:27 (banckmark+0x1be0)
#1 Producer(Queue<int>&, int) /home/taskflow/demo/banckmark.cpp:48 (banckmark+0x14d0)
#2 void std::__invoke_impl<void, void (*)(Queue<int>&, int), std::reference_wrapper<Queue<int> >, int>(std::__invoke_other, void (*&&)(Queue<int>&, int), std::reference_wrapper<Queue<int> >&&, int&&) /usr/include/c++/10/bits/invoke.h:60 (banckmark+0x33d4)
#3 std::__invoke_result<void (*)(Queue<int>&, int), std::reference_wrapper<Queue<int> >, int>::type std::__invoke<void (*)(Queue<int>&, int), std::reference_wrapper<Queue<int> >, int>(void (*&&)(Queue<int>&, int), std::reference_wrapper<Queue<int> >&&, int&&) /usr/include/c++/10/bits/invoke.h:95 (banckmark+0x31ec)
#4 void std::thread::_Invoker<std::tuple<void (*)(Queue<int>&, int), std::reference_wrapper<Queue<int> >, int> >::_M_invoke<0ul, 1ul, 2ul>(std::_Index_tuple<0ul, 1ul, 2ul>) /usr/include/c++/10/thread:264 (banckmark+0x3068)
#5 std::thread::_Invoker<std::tuple<void (*)(Queue<int>&, int), std::reference_wrapper<Queue<int> >, int> >::operator()() /usr/include/c++/10/thread:271 (banckmark+0x2fdc)
#6 std::thread::_State_impl<std::thread::_Invoker<std::tuple<void (*)(Queue<int>&, int), std::reference_wrapper<Queue<int> >, int> > >::_M_run() /usr/include/c++/10/thread:215 (banckmark+0x2f88)
#7 <null> <null> (libstdc++.so.6+0xccf98)
Location is heap block of size 4096 at 0xfffff4103000 allocated by main thread:
#0 operator new[](unsigned long) ../../../../src/libsanitizer/tsan/tsan_new_delete.cpp:70 (libtsan.so.0+0x8fe9c)
#1 Queue<int>::Queue() /home/taskflow/demo/banckmark.cpp:23 (banckmark+0x1d28)
#2 TestSingleProducerSingleConsumer() /home/taskflow/demo/banckmark.cpp:64 (banckmark+0x1630)
#3 main /home/taskflow/demo/banckmark.cpp:80 (banckmark+0x17a0)
Thread T1 (tid=42566, running) created by main thread at:
#0 pthread_create ../../../../src/libsanitizer/tsan/tsan_interceptors_posix.cpp:962 (libtsan.so.0+0x61880)
#1 std::thread::_M_start_thread(std::unique_ptr<std::thread::_State, std::default_delete<std::thread::_State> >, void (*)()) <null> (libstdc++.so.6+0xcd2bc)
#2 TestSingleProducerSingleConsumer() /home/taskflow/demo/banckmark.cpp:66 (banckmark+0x1668)
#3 main /home/taskflow/demo/banckmark.cpp:80 (banckmark+0x17a0)
Thread T2 (tid=42567, running) created by main thread at:
#0 pthread_create ../../../../src/libsanitizer/tsan/tsan_interceptors_posix.cpp:962 (libtsan.so.0+0x61880)
#1 std::thread::_M_start_thread(std::unique_ptr<std::thread::_State, std::default_delete<std::thread::_State> >, void (*)()) <null> (libstdc++.so.6+0xcd2bc)
#2 TestSingleProducerSingleConsumer() /home/taskflow/demo/banckmark.cpp:67 (banckmark+0x1698)
#3 main /home/taskflow/demo/banckmark.cpp:80 (banckmark+0x17a0)
SUMMARY: ThreadSanitizer: data race /home/taskflow/demo/banckmark.cpp:37 in Queue<int>::pop()
==================
数据竞争 1
涉及的线程:线程 T1 和线程 T2。 涉及的变量:Queue 对象中的成员变量(具体位置未明确给出),被解释为栈上的地址 0xfffffffff248 或 。 读写操作:
线程 T2 在 Producer(Queue&, int) 函数内调用 Queue::push(int const&)(第 28 行)对队列进行写入操作。
线程 T1 在 Consumer(Queue&, int) 函数内调用 Queue::pop()(第 32 行)对队列进行读取操作。 数据竞争:线程 T1 和线程 T2 并发地对 Queue 对象进行读写操作,且没有采用适当的同步机制(如互斥锁、条件变量等)来保护对队列的访问,导致数据竞争。
数据竞争 2
涉及的线程:线程 T1 和线程 T2。 涉及的变量:Queue 对象在堆上分配的内存区域(地址 0xfffff4103000,大小为 4096 字节)。 读写操作:
线程 T2 在 Producer(Queue&, int) 函数内调用 Queue::push(int const&)(第 27 行)对队列进行写入操作。
线程 T1 在 Consumer(Queue&, int) 函数内调用 Queue::pop()(第 37 行)对队列进行读取操作。 数据竞争:线程 T1 和线程 T2 并发地对 Queue 对象在堆上分配的内存区域进行读写操作,且没有采用适当的同步机制来保护对队列的访问,导致数据竞争。
对于一般的并发问题,都可以使用该工具进行排查,并根据建议修改代码。
参考
https://github.com/google/sanitizers/wiki/ThreadSanitizerCppManual
标签:std,invoke,thread,int,安全检查,编程,c++,线程,banckmark From: https://blog.csdn.net/Jj147258369/article/details/137373749