我对信号量的实现方案很感兴趣,我了解到在x86中,我们可以使用"锁前缀"来实现原子操作,我想用它来实现互斥,我知道C++11现在有标准的互斥,但我想实现我自己的互斥。这是我的代码:
#include <iostream>
#include <thread>
#include <vector>
struct Semaphore
{
private:
int s;
public:
Semaphore( int s ) : s(s){}
void wait()
{
int *p = &s;
_asm
{
mov eax, p
lock dec DWORD PTR [eax]
begin : mov ebx, DWORD PTR [eax]
cmp ebx, 0
jl begin
};
}
void signal()
{
int *p = &s;
_asm
{
mov eax, p
lock inc DWORD PTR [eax]
};
}
} s(1);
void print()
{
s.wait();
std::cout << "Print Thread " << std::this_thread::get_id() << std::endl;
s.signal();
}
int main( int argc, char* argv )
{
std::vector< std::thread > vec;
int n = 3;
for( int i = 0; i < n; ++i ) vec.push_back( std::thread( print ) );
for( int i = 0; i < n; ++i ) vec[i].join();
return 0;
}
问题是,当有两个线程时,代码运行良好,而在有三个线程的情况下,程序似乎陷入了死锁状态,有人能解释为什么吗?或者给我一些关于如何在x86机器上实现is的建议吗?
您的wait
实际上是一个spinlock——当锁处于争用状态时,它将(尝试)使用100%的CPU,直到其他线程释放信号量。不幸的是,由于它使用了100%的CPU,这会阻止其他线程获得CPU时间,因此您会得到接近死锁的东西。
据猜测,您可能正在双核CPU上运行。在这种情况下,另一个线程可以全速运行,即使自旋锁处于紧张的循环中,也会浪费CPU时间。当获得的线程数超过可用的CPU核心数时,事情就会陷入停顿。
如果您有充分的理由相信信号量会很快被清除(在这种情况下,您希望避免任务切换的开销),那么旋转锁可能会很有用。然而,在一个典型的情况下,你想限制花在"旋转"上的时间,所以你的循环看起来像:
mov ecx, 100
begin : mov ebx, DWORD PTR [eax]
test ebx, ebx
loopnz begin
然后,在它脱离循环后,检查信号量是否已清除,或者是否达到了极限(在本例中为100次迭代)。如果达到了限制,则调用调度程序让其他线程运行(并在该线程再次运行时重试等待)。
您创建的代码不是信号量的正确实现。信号量应该将等待任务放入信号量的等待队列;在那之后,它的代码不运行,直到信号量再次被发信号;当信号量被发出信号时,一个等待的线程被唤醒。一半的信号量代码在内核中,关于如何访问它的详细信息在线程库实现中。因此,为了正确地实现信号量,在C++中,您需要做一些更复杂的事情。或者你可以编写自己的操作系统。
此外,您没有说明您使用的编译器,但您的编译器可能过于激进地优化了asm子句。
这里有多个问题。这里有两个。
-
您的wait()例程无条件递减计数器。如果你有两个服务员,那么你的计数将是-2,在任何服务员停止等待之前,你需要两个信号。
-
编写的信号量代码完全依赖于调度程序。因此,根据调度器以及等待和信号器的优先级,等待任务(繁忙循环)完全有可能永远不会屈服于另一个执行上下文。
希望这能有所帮助。
是的,我承认如果没有操作系统或机器的帮助,我无法实现我自己的版本,所以我尝试使用C++11标准来实现它,我发现斯坦福大学的一门课程提供了一个解决方案,我想与任何需要它的人分享它,下面是链接:http://www.stanford.edu/class/cs110/lectures/18-threading-and-semaphores.html#(3)