【问题标题】:Semaphore implementation信号量实现
【发布时间】:2011-08-25 10:24:05
【问题描述】:

我想知道是否有办法在 C++(或 C#)中实现信号量,任何有帮助的库。我尝试使用 OpenMP,但实际上无法阻塞线程,而是我不得不忙着等待它们,如果/当我没有足够数量的线程时,这会导致死锁。所以首先我正在寻找一个可以让我阻止/生成/杀死我的线程的库。
其次,是否有任何库已经实现了信号量?
最后,当我被介绍到信号量的上下文时,我发现它非常有用(也许我错了?)但我没有看到很多库(如果有的话)实现它。我熟悉 OpenMP,查看了 Intel 的 TBB、C# 线程。但在这些中,我没有明确地看到信号量。那么信号量不是我想的那么实用吗?还是它们难以实施?还是我不知道?
附:
信号量可以跨平台实现吗?因为它们可能与操作系统有关。

【问题讨论】:

  • OpenMP 中有 omp_set_lock/ omp_unset_lock,pthread 支持互斥锁(TBB 几乎肯定也支持,尽管我从未使用过)。最后,如果您不反对 Boost,则可以使用其中的信号量和互斥量实现。上述所有内容都是跨平台的(分层 OS 特定功能)。一般来说,如果没有依赖于操作系统的代码,您就无法实现非忙同步,但这正是这些库的用途。
  • @Damon 我对 OMP 锁很熟悉,但是如果你稍微研究一下,你会发现一个线程无法取消它没有设置的锁。
  • 这是正确的,但即使有这个限制,它仍然为您提供了实现任何其他同步所需的基本功能。当然,我提到的其他库在这方面更加用户友好。

标签: c# c++ implementation semaphore


【解决方案1】:

是否有任何库已经实现了这一点?
对于 C++,有多个多线程库,它们提供了 Semaphore 实现:

此外,您还可以使用 Boost 实现信号量。签出this

【讨论】:

  • 谢谢,有首选的图书馆吗?首选因为更易于学习和使用。听说要用posix写太多代码才能启动线程。
  • @AtoMerZ:出于多种原因,我建议使用 POSIX。如果您遇到问题,它提供了可移植性和广泛的支持。就编写太多代码而言,它只是让您在想要实现的配置或线程模型上获得更大的灵活性。
  • POSIX 是否也允许手动阻塞线程/进程?如果是这样,那么我认为这就是我想要的。
  • @AtoMerZ:是的,POSIX 为线程和进程同步提供互斥锁和信号量。
【解决方案2】:

第一个建议使用 boost。所有的辛苦工作都完成了。

如果你想看看它是如何实现的,它应该看起来像这样(虽然这是一个粗略的草图,但我相信通过一些研究可以优化它)。基本上,信号量是由三件事构成的:

  • 计数
  • 条件变量(提供暂停)
  • 一个互斥体,它提供了修改计数和等待条件的独占性。

这是简单的版本:

#include <pthread.h>

// Need an exception safe locking class.
struct MutexLocker
{
    MutexLocker(pthread_mutex_t& m) :mutex(m)
    { if (pthread_mutex_lock(&mutex) != 0)      {throw int(1); }}
    ~MutexLocker()
    { if (pthread_mutex_unlock(&mutex) != 0)    {throw int(1); }}
    private:
        pthread_mutex_t&    mutex;
};

class Semaphore
{
    public:
        Semaphore(int initCount = 0)
            : count(initCount)
            , waitCount(0)
        {
            if (pthread_mutex_init(&mutex, NULL) != 0)
            {   throw int(1);
            }

            if (pthread_cond_init(&cond, NULL) != 0)
            {   pthread_mutex_destroy(&mutex);
                throw int(2);
            }
        }

        void wait()
        {
            MutexLocker locker(mutex);

            while(count == 0)
            {
                ++waitCount;
                if (pthread_cond_wait(&cond, &mutex) != 0)
                {   throw int(2);
                }

                // A call to pthread_cond_wait() unlocks the mutex and suspends the thread.
                // It does not busy wait the thread is suspended.
                //
                // When a condition variable receives apthread_cond_signal() a random thread
                // is un-suspended. But it is not released from the call to wait
                // until the mutex can be reacquired by the thread.
                //
                // Thus we get here only after the mutex has been locked.
                //
                // You need to use a while loop above because of this potential situation.
                //      Thread A:  Suspended waiting on condition variable.
                //      Thread B:  Working somewhere else.
                //      Thread C:  calls signal() below (incrementing count to 1)
                //                 This results in A being awakened but it can not exit pthread_cond_wait()
                //                 until it requires the mutex with a lock. While it tries to
                //                 do that thread B finishes what it was doing and calls wait()
                //                 Thread C has incremented the count to 1 so thread B does not
                //                 suspend but decrements the count to zero and exits.
                //                 Thread B now aquires the mutex but the count has been decremented to
                //                 zero so it must immediately re-suspend on the condition variable.


                // Note a thread will not be released from wait until
                // it receives a signal and the mustex lock can be re-established.

                --waitCount;
            }

            --count;
        }

        void signal()
        {

            // You could optimize this part with interlocked increment.
            MutexLocker locker(mutex);
            ++count;

            // This Comment based on using `interlocked increment` rather than mutex.
            //
            // As this part does not modify anything you don;t actually need the lock.
            // Potentially this will release more threads than you need (as you don't
            // have exclusivity on reading waitCount but that will not matter as the
            // wait() method does and any extra woken threads will be put back to sleep.

            // If there are any waiting threads let them out.
            if (waitCount > 0)
            {   if  (pthread_cond_signal(&cond) != 0)
                {   throw int(2);
                }
            }
        }
    private:
        unsigned int        count;
        unsigned int        waitCount;
        pthread_mutex_t     mutex;
        pthread_cond_t      cond;
};

【讨论】:

  • @Martin 谢谢你,正如我上面所说的,我正在寻找的是一种阻止我的线程的方法。我不熟悉 pthreads,但我认为您的代码正在忙于等待,因为您有 while(count==0)。你提到了使用内部忙于等待的自旋锁,对吗?如果不是这样,您能否解释一下您是如何阻塞线程的。
  • @AtoMerZ:绝对没有使用忙等待。条件变量暂停一个调用wait()的线程,直到它收到一个信号。需要 while 循环来确保在释放的线程获得锁之前另一个线程没有窃取增量计数。没有经验的程序员的一个常见错误是使用if 而不是while
  • @AtoMerZ:(我已经更新了 cmets 以解释为什么需要 while)。笔记。如果你不知道condition variable 是什么,你绝对不应该自己尝试这样做。使用预先构建的信号量之一。所有其他线程控制都是从互斥锁和条件变量构建的(这些是构建其他所有东西的最基本的构建块)。
  • @Martin 再次感谢,cmets 提供了帮助。我还阅读了有关条件变量的信息,现在我了解了它们。尽管我心中仍有疑问,但我想我找到了很多我想要的东西。
  • @Martin 根据 [this][1] a spinlock is a 'busywait' one. 我想。 [1]:*.com/questions/195853/spinlock-versus-semaphore/…
【解决方案3】:

在 .NET 中,BCL 中有一个实现:System.Threading.Semaphore

对于 Windows 上的本机代码,请查看 CreateSemaphore Function。如果您的目标是 Linux,那么您可以找到维也纳科技大学 here 的信号量实现(我之前已经使用过并且可以使用)。

【讨论】:

    【解决方案4】:

    在 C++ 中,对于阻塞线程的方式,我建议您使用条件变量而不是信号量。在 C# 中,monitors 可能更合适。

    即使对于Producer-Consumer problem 的一个相当简单的情况,基于信号量的解决方案也更难正确执行:以错误的顺序执行信号量递增和递减可能会导致问题。相反,基于条件变量的解决方案不会有这样的问题:条件变量与锁(互斥锁)一起使用,并且自动施加正确的操作顺序;所以在唤醒之后,一个线程已经获得了锁。

    另请参阅我对When should I use semaphores? 的回答,我在其中给出了另一个条件变量示例,我认为它更适合通常用信号量解决的问题。

    为了解决您的另一个问题,我认为对错误使用的更高责任和更高的解决方案复杂性(与替代方案相比)是某些线程包不提供信号量的原因。对于 TBB,我可以肯定地说。 C++11 中的线程支持(在 Boost.Thread 之后设计)也没有;请参阅Anthony Williams' answer 为什么。

    【讨论】: