11

二元信号量和互斥锁的区别~Finally, the event is born.

 3 years ago
source link: https://zhuanlan.zhihu.com/p/152269322
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

二元信号量和互斥锁的区别~Finally, the event is born.

一个人NB的不是标签

如果你把标题百度一下,那么得到的是一堆长串的解释,结果没有几篇是讲到点上的,而且误人子弟。

谷歌好一点, 搜出来的这篇讲得不错 semaphore和mutex的区别? - fleuria的回答 - 知乎 https://www.zhihu.com/question/47704079/answer/136200849

实际上它们的区别很简单,mutex是用于保护数据的锁,一次只能由一个线程访问。而semaphore是用于资源的同步,比如资源可用了,资源不可用需要等待。

所以它们的区别很大,二元信号量不能认为等于互斥锁。使用场景不一样,需要区分清楚,大部分时候使用Mutex都是可以实现目的。少部分需要资源同步的可以使用信号量。

信号量的常见实现是使用条件变量(condition variable,又叫completion variable)。所以很多时候也会使用条件变量代替信号量。

有时候, 人们会将条件标量抽象成Event(事件)提供更加简洁的接口用于资源的同步或者事件的发生。

为什么呢?

因为条件变量太难用了,以C++为例,搜索一下condition variable C++, 我们可以进入cppreference https://en.cppreference.com/w/cpp/thread/condition_variable (将里面的代码复制如下,便于讨论)。

从这代码,可以看到条件变量要配合mutex使用,还要有资源(ready flag), 我们要知道什么时候unlock,什么时候使用lock mutex 等等(我看了几遍,都忘了,每次写都要参考一下)。

#include <iostream>
#include <string>
#include <thread>
#include <mutex>
#include <condition_variable>
 
std::mutex m;
std::condition_variable cv;
std::string data;
bool ready = false;
bool processed = false;
 
void worker_thread()
{
    // Wait until main() sends data
    std::unique_lock<std::mutex> lk(m);
    cv.wait(lk, []{return ready;});
 
    // after the wait, we own the lock.
    std::cout << "Worker thread is processing data\n";
    data += " after processing";
 
    // Send data back to main()
    processed = true;
    std::cout << "Worker thread signals data processing completed\n";
 
    // Manual unlocking is done before notifying, to avoid waking up
    // the waiting thread only to block again (see notify_one for details)
    lk.unlock();
    cv.notify_one();
}
 
int main()
{
    std::thread worker(worker_thread);
 
    data = "Example data";
    // send data to the worker thread
    {
        std::lock_guard<std::mutex> lk(m);
        ready = true;
        std::cout << "main() signals data ready for processing\n";
    }
    cv.notify_one();
 
    // wait for the worker
    {
        std::unique_lock<std::mutex> lk(m);
        cv.wait(lk, []{return processed;});
    }
    std::cout << "Back in main(), data = " << data << '\n';
 
    worker.join();
}

其实这个代码的逻辑很清晰:worker_thread 等待main thread 发送数据,然后main thread 等worker thread处理完数据。

所以我们可以将等待发送数据抽象成data ready event(数据准备好的事件),处理完数据抽象成data finished event。这样子,worker thread可以等待data ready event, 然后触发data finished event;而main thread则是触发data ready event, 然后等待data finished event.

伪代码如下

worker thread:
  data_ready_event.wait()
  process data
  data_finished_event.trigger()

main thread:
  prepare data
  data_ready_event.trigger()
  data_finished_event.wait()
  

看起来是不是很清晰,让我们看看真正的C++代码如何。

#include <iostream>
#include <string>
#include <thread>
#include <mutex>
#include <memory>

std::shared_ptr<Event> data_ready_event = Event::create_event();
std::shared_ptr<Event> data_finished_event = Event::create_event();
std::string data;

void worker_thread()
{
    // Wait until main() sends data
    data_ready_event->wait();
    std::cout << "Worker thread is processing data\n";
    data += " after processing";

    // Send data back to main()
    std::cout << "Worker thread signals data processing completed\n";

    data_finished_event->trigger();
}

int main()
{
    std::thread worker(worker_thread);

    data = "Example data";
    // send data to the worker thread
    std::cout << "main() signals data ready for processing\n";
    data_ready_event->trigger();

    // wait for data finish
    data_finished_event->wait();
    std::cout << "Back in main(), data = " << data << '\n';

    worker.join();
}

通过上面的代码,可以看到借助event, 我们再也不用关心如何繁琐的使用条件变量, 我们只需要使用event.wait(), event.trigger()就可以实现同步。而event本身很简单,并且我们可以重复使用,请看

#include <iostream>
#include <string>
#include <thread>
#include <mutex>
#include <memory>

class Event
{
private:
    bool triggered = false;
    std::mutex mMutex;
    std::condition_variable cv;

public:
    static std::shared_ptr<Event> create_event()
    {
        return std::make_shared<Event>();
    }
    void trigger()
    {
        {
            std::unique_lock<std::mutex> lk(mMutex);
            triggered = true;
        }
        cv.notify_one();
    }
    void wait()
    {
        std::unique_lock<std::mutex> lk(mMutex);
        cv.wait(lk, [this] { return triggered; });
    }
};

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK