【发布时间】:2019-08-28 13:52:54
【问题描述】:
我试图了解spinlock mutex 的工作原理, 所以我写了一个简单的代码(如下所示)来测量指令的交错 受自旋锁(或 std::) 互斥锁保护的不同线程。
令人惊讶的是,它显示(至少在 gcc 中)std::mutex(与自旋锁互斥锁相反) 似乎有利于拥有它的线程,导致非常小的指令交错(最多 5%), 除非有问题的指令非常快(比如增加一个计数器)。 在这种情况下,我们甚至可以得到 50%。自旋锁互斥体提供至少 80%(通常超过 90%)。
这是众所周知的事实吗?或者我下面的代码可能有错误?
我的意思是,我知道一个经验法则,即互斥体应始终锁定最短时间。 但我确信确实如此,因为我们想减少线程的序列化, 而不是因为 std::mutex 偏爱拥有线程...
代码如下:
#include<atomic>
#include<thread>
#include<iostream>
#include<chrono>
#include<mutex>
class SpinLockMutex{
std::atomic_flag m_flag = ATOMIC_FLAG_INIT;
public:
void lock() { while( m_flag.test_and_set(std::memory_order_acquire) ) /*do nothing*/ ; }
void unlock() { m_flag.clear(std::memory_order_release) ; }
};//class SpinLockMutex
// ******************************************
// // // std::mutex vs SpinLockMutex
//SpinLockMutex globalMutex;
std::mutex globalMutex;
// ******************************************
// This class helps to start threads at the same time :
class Starter{
mutable std::mutex m_m;
bool m_ready = false;
public:
bool isReady() const { std::lock_guard<std::mutex> guard(m_m);
return m_ready;
}
void start() { std::this_thread::sleep_for(std::chrono::seconds(3));
std::lock_guard<std::mutex> guard(m_m);
m_ready = true;
}
};//class Starter
constexpr std::size_t LOOP_SIZE = 100;
std::size_t previous_thread_repeated = 0;
Starter starter;
void mainFcnForThread ()
{
static std::thread::id previous_thread_id = std::this_thread::get_id();
while(!starter.isReady())
; //do nothing
for(std::size_t i = 0; i!=LOOP_SIZE ; ++i){
globalMutex.lock();
if(previous_thread_id == std::this_thread::get_id() ) {
++previous_thread_repeated;
std::this_thread::sleep_for(std::chrono::microseconds(100));
}
previous_thread_id = std::this_thread::get_id();
globalMutex.unlock();
}
}//void mainFcnForThread
int main()
{
std::thread t1(mainFcnForThread);
std::thread t2(mainFcnForThread);
starter.start();
t1.join();
t2.join();
std::cout << double(previous_thread_repeated)/(2*LOOP_SIZE) << '\n';
return 0;
}
【问题讨论】:
-
请注意
sleep_for(t)至少会休眠t,但没有上限。 -
操作系统可能已经取消了这些线程的优先级,这些线程都在等待
starter大约 3 秒。更公平的测试可能是使用适当的信号,例如两个线程的条件变量来发出信号,然后启动器可以立即释放它们。无论哪种方式,涉及跨越 10 微秒的 100 次迭代的测试听起来都不够。如果您运行测试一分钟,您会得到相同的结果吗? -
通常它是首选的,因为它仍然在运行,不像其他正在等待/休眠的线程。
-
锁是否公平在一定程度上取决于操作系统,Linux 和 Windows 都随着时间而改变。
-
仅供参考:您的
for循环几乎 100% 地保持互斥锁锁定。如果您的目标是演示互斥锁的属性,这可能是一个有用的技巧,但在任何实际程序中它几乎总是一个坏主意。最好的多线程程序被设计成不需要经常使用互斥锁,并且不会让任何互斥锁锁定超过程序总运行时间的一小部分。
标签: c++ multithreading