C/C++ 多线程编程之信号量及其实现代码 semaphore类

    仅仅用于多线程同步,不考虑互斥,即不存在共享数据的竞争,我们应该使用信号量,信号量比条件变量高效得多,更利于控制多线程同步,如果要用条件变量模拟信号量,则不得不考虑很多细节。不应该使用信号量代替互斥锁的功能。

    千万不要误以为用条件变量和互斥锁可以简单地实现一个信号量,这样实现的信号量很可能不能正常工作。一般而言,Windows下是用信号量实现的条件变量,所以信号量更接近于底层。下面将来看看用条件变量如何正确实现信号量:

//c++11,已确保无误,2019/10/10,优化
#include <atomic>
#include <mutex>
#include <condition_variable>
#if _SEMAPHORE_NEED_SECURITY_
#if 0/* 是否采用自旋锁,2019/10/19 */
class sem_lock
{//自旋锁,lock和unlock可以分离,当然可以用信号量代替自旋锁
    std::atomic_flag flag = ATOMIC_FLAG_INIT;
public:
    void lock()
    {
        while (flag.test_and_set(std::memory_order_acquire));
    }
    void unlock()
    {
        flag.clear(std::memory_order_release);
    }
};
#else
class sem_lock
{//二值信号量,lock和unlock可以分离
    unsigned int count = 1;
    std::mutex mut;
    std::condition_variable cv;
public:
    void lock()
    {
        std::unique_lock<std::mutex> lk(mut);
        if (count == 0)cv.wait(lk);
        count = 0;
    }
    void unlock()
    {
        mut.lock();
        count = 1;
        cv.notify_one();
        mut.unlock();
    }
};
#endif
/**
 * 由于Win平台下,条件变量是用信号量实现的,
 * “准备唤醒等待线程的时间点”(在此之后才会发信)不在notify函数开头而在其内部,
 * 在此之前进入等待的线程都会竞争信号,在此之后的线程不会参与竞争,所以发信者不会竞争这个信号。
 * 但是,用条件变量发信时,发信者会获取互斥锁,将要等待的线程也要先获取这个互斥锁(释放锁的线程更容易再次获得锁)。
 * 所以,条件变量和互斥锁简单地实现信号量时,该信号量若提前发信,会导致发信者发信后有更大几率获取这个信号。
 * 也就是说,存在严重的信号竞争问题,信号量的竞争应该尽量做到公平,为此,使用二值信号量达到这一目的。
 * 这是一个安全的信号量,它要求释放信号的线程不会与等待信号的线程竞争
 * 注意某些情况导致的信号竞争,会使发信未唤醒等待者,从而死等
**/
class semaphore
{//确保信号量不被发送者独吞
    unsigned int count;
    unsigned int waiters;
    int blocks;
    sem_lock slk;//二值信号量,为保证进入wait()函数的线程先获取信号量
    std::mutex mut;
    std::condition_variable cv;
public:
    semaphore(unsigned int value):count(value),waiters(0),blocks(-1)
    {
#ifdef _DEBUG__CXX_SEMAPHORE__
        std::cout<<"c++11"<<std::endl;
#endif
    }

    void wait()
    {
        slk.lock();
        std::unique_lock<std::mutex> lk(mut);
        waiters++;
        slk.unlock();
        while (count == 0)cv.wait(lk);
        waiters--;
        --count;
        if (waiters == blocks)
        {//在发信期间,所有非const函数都将阻塞
            slk.unlock();
            blocks = -1;
        }
    }

    bool trywait()
    {
        slk.lock();
        std::lock_guard<std::mutex> lk(mut);
        slk.unlock();
        if (count == 0)return false;
        --count;
        return true;
    }

    template<class _Clock, class _Duration>
      bool wait_until(const std::chrono::time_point<_Clock, _Duration>& __atime)
    {
        slk.lock();
        std::unique_lock<std::mutex> lk(mut);
        waiters++;
        slk.unlock();
        if (cv.wait_until(lk,__atime,[&] {
            return count != 0;
        })) {
            waiters--;
            --count;
            if (waiters == blocks)
            {
                slk.unlock();
                blocks = -1;
            }
            return true;
        }
        waiters--;
        if (!(waiters|blocks))
        {
            slk.unlock();
            blocks = -1;
        }
        else if (blocks > 0)blocks--;
        return false;
    }

    template<typename _Rep, typename _Period>
      bool wait_for(const std::chrono::duration<_Rep, _Period>& __rtime)
    {
        slk.lock();
        std::unique_lock<std::mutex> lk(mut);
        waiters++;
        slk.unlock();
        if (cv.wait_for(lk,__rtime,[&] {
            return count != 0;
        })) {
            waiters--;
            --count;
            if (waiters == blocks)
            {
                slk.unlock();
                blocks = -1;
            }
            return true;
        }
        waiters--;
        if (!(waiters|blocks))
        {
            slk.unlock();
            blocks = -1;
        }
        else if (blocks > 0)blocks--;
        return false;
    }

    void post()
    {
        slk.lock();
        mut.lock();
        ++count;
        if (waiters != 0)
        {//连续发信将被阻塞
            blocks = waiters - 1;
            cv.notify_one();
        }
        else slk.unlock();
        mut.unlock();
    }

    void post(unsigned int n)
    {
        slk.lock();
        mut.lock();
        count += n;
        if (waiters != 0)
        {
            blocks = waiters > n ? waiters - n : 0;
            cv.notify_all();
        }
        else slk.unlock();
        mut.unlock();
    }

    unsigned int get_value() {return count;}
};
#else
class semaphore
{//不能确保信号量不被发送者独吞
    unsigned int count;
    std::mutex mut;
    std::condition_variable cv;
public:
    semaphore(unsigned int value):count(value)
    {
#ifdef _DEBUG__CXX_SEMAPHORE__
        std::cout<<"c++11"<<std::endl;
#endif
    }

    void wait()
    {
        std::unique_lock<std::mutex> lk(mut);
        while (count == 0)cv.wait(lk);
        --count;
    }

    bool trywait()
    {
        std::lock_guard<std::mutex> lk(mut);
        if (count == 0)return false;
        --count;
        return true;
    }

    template<class _Clock, class _Duration>
      bool wait_until(const std::chrono::time_point<_Clock, _Duration>& __atime)
    {
        std::unique_lock<std::mutex> lk(mut);
        if (cv.wait_until(lk,__atime,[&] {
            return count != 0;
        })) {
            --count;
            return true;
        }
        return false;
    }

    template<typename _Rep, typename _Period>
      bool wait_for(const std::chrono::duration<_Rep, _Period>& __rtime)
    {
        std::unique_lock<std::mutex> lk(mut);
        if (cv.wait_for(lk,__rtime,[&] {
            return count != 0;
        })) {
            --count;
            return true;
        }
        return false;
    }

    void post()
    {
        mut.lock();
        ++count;
        cv.notify_one();
        mut.unlock();
    }

    void post(unsigned int n)
    {
        mut.lock();
        count += n;
        cv.notify_all();
        mut.unlock();
    }

    unsigned int get_value() {return count;}
};
#endif

简单实现的信号量是不安全的,发信者很可能独吞其发送的信号,这是由于互斥锁机制会让释放锁的线程再次得锁几率很大(导致发生信号的争抢)导致的。所以,我们要确保信号的公平竞争(实现这个几乎不可能),退而求其次,让发信者在有等待者的情况下先让等待者获取信号,使用自旋锁或简单二值信号量便达到了这一目的。

为了使发信后能够迅速返回而不等待收信者,再新增了一个整型类型,这使发信更有效率,但收信未完成之前会阻塞所有相关函数。

    另外,附上个人写的semaphore的源码,里面有对c语言信号量的c++封装。

https://download.csdn.net/download/qq_25675517/11833939

注:里面关于win平台下get_value()的实现是错误的,关于c+++11标准用条件变量和互斥锁实现的信号量,代码和上述不一样,以上述为准(效率更高且正确)。

发布了6 篇原创文章 · 获赞 1 · 访问量 388

猜你喜欢

转载自blog.csdn.net/qq_25675517/article/details/102251013