【问题标题】:Semaphore implementation using x86 assembly使用 x86 程序集实现信号量
【发布时间】:2013-08-16 15:12:43
【问题描述】:

我对信号量实现方案很感兴趣,我了解到在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;
}

问题是,当有两个线程时,代码运行良好,而在 3 个线程的情况下,程序似乎陷入死锁状态,谁能解释原因或给我一些关于如何实现的建议x86 机器?

【问题讨论】:

  • 就像 Antti 所说,没有操作系统支持,您无法实现任何类型的健全信号量。
  • 正确的互斥量或信号量不能纯粹在用户空间中实现,因为它需要与操作系统的调度程序通信。

标签: c++ assembly concurrency operating-system semaphore


【解决方案1】:

您的wait 确实是一个自旋锁——当锁处于争用状态时,它将(尝试)使用 100% 的 CPU,直到其他线程释放信号量。不幸的是,由于它使用了 100% 的 CPU,这会阻止其他线程获得 CPU 时间,因此您会得到一些接近死锁的东西。

猜测,您可能在双核 CPU 上运行。在这种情况下,其他线程可以全速运行,即使自旋锁处于紧密循环中,浪费 CPU 时间。当您获得的线程数超过可用 CPU 内核数时,事情就会停止。

如果您有充分的理由相信信号量会很快被清除(在这种情况下您希望避免任务切换的开销),自旋锁会很有用。但是,在典型情况下,您希望限制“旋转”所花费的时间,因此您的循环看起来像:

        mov ecx, 100
begin : mov ebx, DWORD PTR [eax]
        test ebx, ebx
        loopnz begin

然后,在它跳出循环后,您检查信号量是否已清除,或者您的限制(在本例中为 100 次迭代)是否已达到。如果达到限制,则调用调度程序让其他线程运行(并在该线程再次运行时重试等待)。

【讨论】:

  • 使用“100% 的 CPU”并不能阻止其他线程获得 CPU 时间 - 没有理智的操作系统会让自己像这样挂起。任何进程/线程/任务都会被 [timer] 中断抢占,例如,当它的时间片用完时,让其他任务继续执行。 CPU 利用率与它无关,与抢占式内核无关。如果您认为我错了,请随时纠正我。
  • @amn:好吧:你错了。是的,只要您的线程以正常优先级运行,它(可能)最终会被中断,而另一个线程将设法运行(这就是我说“接近死锁”的原因而不仅仅是“死锁”)。同时,他有一个线程正在尽最大努力使用所有 CPU,所以另一个线程不会有机会快速运行——而且在一个具有足够高优先级的线程中,不,没有其他东西会得到完全使用该内核(例如,在 Windows 上,“时间关键”线程几乎可以抢占其他所有内容)。
  • 当我说“几乎所有其他东西”时,我的意思是几乎所有东西——它被赋予了足够高的优先级,如果你以该优先级使用所有 CPU 运行某个东西足够长的时间,它可能会导致机器崩溃防止在需要时发生内存刷新。
  • 您没有在回答中提到优先级,我也没有假设。给定相同的优先级,线程 B 最终将释放信号量,即使线程 A 使用“100% CPU”,最终我的意思是在有限时间内。我错了吗?这就是我想澄清的。如果线程 A 旋转等待较低优先级线程 B 持有的锁,那么我当然不会争辩。
  • @amn:是的,通常——但并非总是如此。例如,在 VMS 上,系统没有在时间关键线程之间进行循环调度。一旦启动了时间关键线程,它将继续运行直到完成,即使其他时间关键线程正在等待并准备好运行。
【解决方案2】:

您创建的代码不是信号量的正确实现。信号量应该是把等待任务放到等待队列中等待信号量;之后,它的代码不会运行,直到信号量再次发出信号;当信号量发出信号时,一个等待线程被唤醒。一半的信号量代码在内核中,如何访问它的细节在线程库实现中。因此,要在 C++ 中正确实现信号量,您需要做一些更复杂的事情。或者您可以编写自己的操作系统。

另外,您没有说明您使用的是什么编译器,但您的编译器可能过于激进地优化 asm 子句。

【讨论】:

    【解决方案3】:

    这里有多个问题。这里有两个。

    1. 您的 wait() 例程无条件地递减计数器。如果您有两个服务员,那么您的计数将为 -2,并且您需要两个信号才能让任何服务员停止等待。

    2. 所写的信号量代码完全依赖于调度程序。因此,根据调度程序以及等待者和信号者的优先级,等待任务(它们是繁忙的循环)完全有可能永远不会屈服于另一个执行上下文。

    希望这会有所帮助。

    【讨论】:

      【解决方案4】:

      是的,我承认如果没有操作系统或机器的帮助,我无法实现自己的版本,所以我尝试使用 C++11 标准来实现一个,我发现斯坦福的一门课程给出了一个解决方案,我想与任何需要它的人分享,这是链接:http://www.stanford.edu/class/cs110/lectures/18-threading-and-semaphores.html#(3)

      【讨论】:

        猜你喜欢
        • 2011-02-08
        • 1970-01-01
        • 1970-01-01
        • 2011-11-10
        • 2017-10-03
        • 2011-08-25
        • 2016-08-19
        • 1970-01-01
        相关资源
        最近更新 更多