C++ 具有两个独占锁组的共享锁

C++ 具有两个独占锁组的共享锁,c++,locking,mutex,semaphore,C++,Locking,Mutex,Semaphore,我有两个方法“log”和“measure”,不应该同时执行。 因此,我尝试使用“std::mutex”按如下方式执行此操作: void log(std::string message) { mtx.lock(); someLogFunctionality(); mtx.unlock(); } void measure() { mtx.lock(); someMeasureFunctionality(); mtx.unlock();

我有两个方法“log”和“measure”,不应该同时执行。 因此,我尝试使用“std::mutex”按如下方式执行此操作:

void log(std::string message)
{
    mtx.lock();
    someLogFunctionality();
    mtx.unlock();
}

void measure()
{        
    mtx.lock();
    someMeasureFunctionality();
    mtx.unlock();
}
现在证明,也可以在不锁定的情况下并行多次调用“log”,这同样适用于“measure”。(原因:SomeLogFunctional()和SomeMeasureFunctional()相互干扰,但同一方法可能会被并行调用多次)

当时我看过“std::shared_mutex”,但我有两个问题:

1.)使用shared_互斥锁,我可以对其中一个方法(log或measure)使用lock_shared,但另一个方法必须使用排他锁(并且不能并行执行多次)

2.)我不能使用C++17(我正在使用的环境中的约束)


你对我如何实现这一点有什么建议吗?

你需要一些比共享互斥体更复杂的屏障逻辑(顺便说一句,共享互斥体不是多平台编译的最佳选择)。例如,您可以使用互斥体、条件变量和2个变量进行屏障同步。它不需要CPU,您也可以不使用睡眠进行检查

#include <mutex>
#include <condition_variable>
#include <atomic>

std::atomic<int> log_executors = 0;
std::atomic<int> measure_executors = 0;

std::mutex mutex;
std::condition_variable condition;

void log(std::string message) {
  {
    std::unique_lock<std::mutex> lock(mutex);

    log_executors++;  // Register current executor and prevent from entering new measure executors

    // Wait until all measure executors will go away
    while(measure_executors) {
      condition.wait(lock);  // wait condition variable signal. Mutex will be unlocked during wait
    }
  }

  // here lock is freed
  someLogFunctionality(); // execute logic


  {
    std::unique_lock<std::mutex> lock(mutex);
    log_executors--;  // unregister current execution
    condition.notify_all();  // send signal and unlock all waiters
  }
}

void measure()
{        
  {
    std::unique_lock<std::mutex> lock(mutex);

    measure_executors++;  // Register current executor and prevent from entering new log executors
    while(log_executors) {
      condition.wait(lock);  // wait until all measure executors will gone
    }
  }

  someMeasureFunctionality();

  {
    std::unique_lock<std::mutex> lock(mutex);
    measure_executors--;  // unregister current execution
    condition.notify_all(); // send signal and unlock all waiters
  }
}
#包括
#包括
#包括
std::原子日志执行器=0;
std::原子测量执行器=0;
std::互斥互斥;
std::条件\可变条件;
无效日志(std::字符串消息){
{
std::唯一锁(互斥锁);
log_executors++;//注册当前执行器并防止输入新的度量执行器
//等待所有措施执行者离开
while(措施执行人){
condition.wait(lock);//等待条件变量信号。在等待期间将解锁互斥锁
}
}
//这里的锁被释放了
SomeLogFunctionary();//执行逻辑
{
std::唯一锁(互斥锁);
log_executors--;//注销当前执行
condition.notify_all();//发送信号并解锁所有服务员
}
}
无效度量()
{        
{
std::唯一锁(互斥锁);
measure_executors++;//注册当前执行器并防止输入新的日志执行器
while(日志执行者){
condition.wait(lock);//等待所有度量值执行器都消失
}
}
一些度量函数();
{
std::唯一锁(互斥锁);
measure_executors-->/注销当前执行
condition.notify_all();//发送信号并解锁所有服务员
}
}

您可以使用主锁授予对信号量变量的访问权:

void log(std::string message)
{
    acquire(LOG);
    someLogFunctionality();
    release(LOG);
}

void measure()
{        
    acquire(MEASURE);
    someMeasureFunctionality();
    release(MEASURE);
}

void acquire(int what) {
    for (;;) {
        mtx.lock();
        if (owner == NONE) {
            owner = what;
        }
        if (owner == what) {
            // A LOG was asked while LOG is running
            users[owner]++;
            mtx.unlock();
            return;
        }
        mtx.unlock();
        // Some sleep would be good
        usleep(5000);
    }
}

void release(int what) {
    mtx.lock();
    if (owner != what) {
        // This is an error. How could this happen?
    }
    if (users[what] <= 0) {
        // This is an error. How could this happen?
    }
    users[what]--;
    if (0 == users[what]) {
        owner = NONE;
    }
    mtx.unlock();
}

在上面,请注意,对measure()的第二次调用实际上执行得稍早一些。这应该没问题。但是,如果您想让调用“序列化”,即使它们并行发生,您也需要为每个所有者提供一个堆栈和更复杂的代码。

根据alexb的回复,我编写了以下互斥类,目前它对我有效(目前仅在一个简单的多线程示例应用程序中试用过)

请注意,它不受“饥饿”的保护。简单地说:如果频繁调用lockLogging(反之亦然),则无法确保lockMeasure会获得锁


这是“单线桥梁问题”的一个例子。可以使用信号量完成您想要的操作。我不记得怎么做了,所以我不打算写一个答案,但如果你搜索这个短语,你应该会找到一些指南。也许是递归互斥?顺便说一句,您应该删除C语言标记。C语言没有
std::string
std::shared_mutex
。C语言没有名称空间。请根据需要更新您的语言标记。sleep/spin方法非常难看。
stD::condition_variable
只接受一个函数指针,只需
while(measure_executors){condition.wait(lock);
->
condition.wait(lock,[](){return measure_executors;})
您修改
日志执行器
度量执行器
,而不持有互斥锁。虽然它们是原子的,但这并不能阻止持有互斥锁的代码和访问原子的代码之间的竞争条件。(想想看,互斥锁保护什么?)考虑:线程A正在调用
log
它持有互斥锁。它发现
measure\u executors
是1。然后线程B,调用
measure
并且不持有锁减量
measure\u executors
,然后调用
notify\u all
。然后线程A再次运行,调用
wait
等待某个时间在已发生。此代码是一个灾难。互斥锁不保护任何内容,使
wait
函数容易发生死锁。当然,使用减量锁保护进行修复。还有条件。改为条件。等待(lock,std::chrono::毫秒(毫秒))。等待(lock)可用于完全防止竞争条件幸运的是,我遇到了死锁-两个方法都在“condition.wait(lock);”处停止。假设:“measure”在condition.wait()处停止,因为log_executors==1(这将解锁互斥锁)->在等待时,会再次调用“log”(log_executors=2),并在condition.wait()->第一个“log”处停止Finishs(log_executors=1)->这会唤醒“measure”,但它仍在等待,因为log_executors尚未为0=>死锁,因为log_executors永远不能变为0(“log”也在条件下等待。wait()等待度量完成)解决方案:当我在while循环之后移动increment时,它当前起作用。我使用的方法是创建一个
parallel\u locks
对象,该对象生成单个
parallel\u lock
对象,这些对象是实际的锁,因此可以按照正常的RAII模式与
std::unique\u lock
一起使用。另外请注意:您的解锁方法实际上并不需要d来获取互斥体,如果你很狡猾的话。还有
条件。wait
可以使用谓词,以避免将其放入循环中。互斥体为什么是静态的?它是静态的,因为它在仅标头的库中用作单例。和measure/log ar
void log(std::string message)
{
    acquire(LOG);
    someLogFunctionality();
    release(LOG);
}

void measure()
{        
    acquire(MEASURE);
    someMeasureFunctionality();
    release(MEASURE);
}

void acquire(int what) {
    for (;;) {
        mtx.lock();
        if (owner == NONE) {
            owner = what;
        }
        if (owner == what) {
            // A LOG was asked while LOG is running
            users[owner]++;
            mtx.unlock();
            return;
        }
        mtx.unlock();
        // Some sleep would be good
        usleep(5000);
    }
}

void release(int what) {
    mtx.lock();
    if (owner != what) {
        // This is an error. How could this happen?
    }
    if (users[what] <= 0) {
        // This is an error. How could this happen?
    }
    users[what]--;
    if (0 == users[what]) {
        owner = NONE;
    }
    mtx.unlock();
}
owner is NONE
LOG1 acquires LOG. It can do so because owner is NONE
MEASURE1 acquires LOG. It starts spinning in place because owner != MEASURE
MEASURE2 acquires LOG. It starts spinning in place because owner != MEASURE
LOG2 acquires LOG. It can do so because owner is LOG, users[LOG]=2
LOG2 releases LOG. users[LOG]=1
LOG1 releases LOG. users[LOG]=0, so owner becomes NONE
MEASURE2 by pure chance acquires mtx before MEASURE1, finds owner=NONE and goes
MEASURE1 finds owner=MEASURE and sets users[MEASURE]=2
class MyMutex
{
private:
    std::atomic<int> log_executors;
    std::atomic<int> measure_executors;

    std::mutex mtx;
    std::condition_variable condition;

public:
    MyMutex() : log_executors(0), measure_executors(0) {}
    
    ~MyMutex() {}

    void lockMeasure()
    {   
        std::unique_lock<std::mutex> lock(mtx);

        while(log_executors) {
            condition.wait(lock); 
        }
        measure_executors++; 
    }
    
    void unlockMeasure()
    {   
        std::unique_lock<std::mutex> lock(mtx);

        measure_executors--; 
        if (!measure_executors)
        {
          condition.notify_all();
        }
    }
    
    void lockLogging()
    {         
        std::unique_lock<std::mutex> lock(mtx);

        while(measure_executors) {
          condition.wait(lock); 
        }
        log_executors++;
    }

    void unlockLogging()
    {         
        std::unique_lock<std::mutex> lock(mtx);

        log_executors--; 
        if (!log_executors)
        {
          condition.notify_all(); 
        }
    }

    static MyMutex& getInstance()
    {
        static MyMutex _instance;
        return _instance;
    }    
};
void measure()
{
    MyMutex::getInstance().lockMeasure();

    someMeasureFunctionality();

    MyMutex::getInstance().unlockMeasure();
}

void log()
{
    MyMutex::getInstance().lockLogging();

    someLogFunctionality();

    MyMutex::getInstance().unlockLogging();
}