【问题标题】:C++ std::condition_variable in class context类上下文中的 C++ std::condition_variable
【发布时间】:2022-01-09 23:10:33
【问题描述】:

我正在尝试管理嵌入式系统中相机的优先资源访问。因此,我制作了一个相机接口,它使用 std::condition_variable 来实现这一点。

typedef std::function<void()> UnlockCameraHandler;

class Camera {
    const int MAX_PRIORITY = std::numeric_limits<int>::max();
    int currentPriority = MAX_PRIORITY;
    bool mLockable = true;
    UnlockCameraHandler mUnlockCameraHandler;
    std::condition_variable_any cameraLock;
    std::mutex cameraMut;

public:
    bool isLockable(int priority) {
      std::unique_lock<std::mutex> mLock(cameraMut);
      if (priority < currentPriority) {
        mLock.unlock();
        return true;
      } else {
        mLock.unlock();
        return false;
      }
    }

    void unlock() {
      std::unique_lock<std::mutex> mLock(cameraMut);
      if (mLockable) {
        return;
      }
      currentPriority = MAX_PRIORITY;
      mLockable = true;
      cameraLock.notify_all();
      mLock.unlock();
    }

    bool lock(int priority, UnlockCameraHandler handler) {
      if (priority < 0) {
        return false;
      }
      std::unique_lock<std::mutex> mLock(cameraMut);
      if (priority < currentPriority) {
        if (mUnlockCameraHandler) {
          mUnlockCameraHandler();
        }
      }
      if (!mLockable) {
        cameraLock.wait(mLock, std::bind(&Camera::isLockable, this, priority));
      }
      mUnlockCameraHandler = handler;
      currentPriority = priority;
      mLockable = false;
      mLock.unlock();
      return true;
    }
};

我为这些函数编写了一些 Catch2 单元测试,以确保它可以与多个线程一起使用。

struct CameraLock {
  std::shared_ptr<TestCamera> camera;

  void unlock_handler() { camera->unlock(); }
};

TEST_CASE_METHOD(CameraLock, "Camera lock mechanism") {

  SECTION("lock and unlock camera") {
    camera->lock(100, std::bind(&CameraLock::unlock_handler, this));
    REQUIRE(!camera->isLockable(100));
    camera->unlock();
    REQUIRE(camera->isLockable(100));
  }

  SECTION("lock with higher priority") {
    std::thread low{[&]() {
      REQUIRE(camera->lock(100, std::bind(&CameraLock::unlock_handler, this)));
    }};
    std::thread high{[&]() {
      REQUIRE(camera->lock(50, std::bind(&CameraLock::unlock_handler, this)));
    }};
    low.join();
    high.join();
  }

  SECTION("lock with lower priority") {
    std::thread high{[&]() {
      REQUIRE(camera->lock(50, std::bind(&CameraLock::unlock_handler, this)));
      std::this_thread::sleep_for(std::chrono::milliseconds(10));
      camera->unlock();
    }};

    std::thread low{[&]() {
      REQUIRE(camera->lock(100, std::bind(&CameraLock::unlock_handler, this)));
      camera->unlock();
    }};
    high.join();
    low.join();
    REQUIRE(camera->isLockable(100));
  }

  SECTION("lock with 3 parties") {
    std::thread high{[&]() {
      REQUIRE(camera->lock(50, std::bind(&CameraLock::unlock_handler, this)));
      std::this_thread::sleep_for(std::chrono::milliseconds(10));
      camera->unlock();
    }};

    std::thread low{[&]() {
      REQUIRE(camera->lock(100, std::bind(&CameraLock::unlock_handler, this)));
      camera->unlock();
    }};

    std::thread mid{[&]() {
      REQUIRE(camera->lock(75, std::bind(&CameraLock::unlock_handler, this)));
      std::this_thread::sleep_for(std::chrono::milliseconds(5));
      camera->unlock();
    }};
    high.join();
    low.join();
    mid.join();
    REQUIRE(camera->isLockable(100));
  }
}

它应该以这样的方式工作,当一个进程想要访问相机资源时,它调用 lock() 来保留它,传递一个优先级值和一个回调函数。 Callback 函数的目的是,如果另一个进程想要访问具有更高优先级的摄像头,当前具有访问权限的进程会做适当的工作以释放资源并在之后调用 unlock(),以便优先级进程可以锁定它. 如果优先级较低的进程想要访问资源,它会调用 condition_variable 的 wait() 函数并阻塞,直到优先级进程调用 unlock()。

单元测试在大多数情况下都会通过,但有时我会遇到分段错误或双重 free() 错误(尤其是在“与 3 方锁定”部分),这告诉我我的实现不是线程安全。

下面是一个最小的代码示例,供所有可能想要调试它的人运行:

#include <condition_variable>
#include <mutex>
#include <thread>
#include <functional>
 
typedef std::function<void()> UnlockCameraHandler;
 
class Camera {
    const int MAX_PRIORITY = std::numeric_limits<int>::max();
    int currentPriority = MAX_PRIORITY;
    bool mLockable = true;
    UnlockCameraHandler mUnlockCameraHandler;
    std::condition_variable_any cameraLock;
    std::mutex cameraMut;
 
public:
    bool isLockable(int priority) {
      if (priority < currentPriority) {
        mLock.unlock();
        return true;
      } else {
        mLock.unlock();
        return false;
      }
    }
 
    void unlock() {
      std::unique_lock<std::mutex> mLock(cameraMut);
      if (mLockable) {
        return;
      }
      currentPriority = MAX_PRIORITY;
      mLockable = true;
      cameraLock.notify_all();
      mLock.unlock();
    }
 
    bool lock(int priority, UnlockCameraHandler handler) {
      if (priority < 0) {
        return false;
      }
      std::unique_lock<std::mutex> mLock(cameraMut);
      if (priority < currentPriority) {
        if (mUnlockCameraHandler) {
          mUnlockCameraHandler();
        }
      }
      if (!mLockable) {
        cameraLock.wait(mLock, std::bind(&Camera::isLockable, this, priority));
      }
      mUnlockCameraHandler = handler;
      currentPriority = priority;
      mLockable = false;
      mLock.unlock();
      return true;
    }
};
 
std::shared_ptr<Camera> camera = std::make_shared<Camera>();
 
void unlock_handler() { camera->unlock(); }
 
int main() {
 
    std::thread high{[&]() {
      camera->lock(50, std::bind(&unlock_handler));
      std::this_thread::sleep_for(std::chrono::milliseconds(10));
      camera->unlock();
    }};
 
    std::thread low{[&]() {
      camera->lock(100, std::bind(&unlock_handler));
      camera->unlock();
    }};
 
    std::thread mid{[&]() {
      camera->lock(75, std::bind(&unlock_handler));
      std::this_thread::sleep_for(std::chrono::milliseconds(5));
      camera->unlock();
    }};
    
    high.join();
    low.join();
    mid.join();
}

有人可以告诉我我的代码有什么问题以及如何调整它以满足我的例外情况吗? 提前致谢!

【问题讨论】:

  • 只是一个您似乎错过的注释:“wait 导致当前线程阻塞,直到通知条件变量或发生虚假唤醒"
  • @TedLyngmo 不应该通过将isLockable 函数传递给wait 来解决这个问题吗?
  • 为什么 m_lockable 和优先级并不总是受互斥代码保护的一部分?
  • @Federico 如果我在重置变量之前在unlock 中获取互斥锁,测试用例有时会冻结,可能正在等待另一个线程解锁互斥锁,这永远不会发生。在这种情况下,我也看到了 double free or corruption (out) 错误。
  • unlock 需要在修改currentPrioritymLockable 之前锁定cameraMut,否则您将遇到竞争条件。 lock 还需要先锁定cameraMut 才能测试mLockable

标签: c++ multithreading


【解决方案1】:

您的unlock 方法锁定了互斥锁:

    void unlock() {
      std::unique_lock<std::mutex> mLock(cameraMut);
      // ...
    }

并由您的 lock 方法调用,并且已持有互斥锁:

    bool lock(int priority, UnlockCameraHandler handler) {
      if (priority < 0) {
        return false;
      }
      std::unique_lock<std::mutex> mLock(cameraMut);
      if (priority < currentPriority) {
        if (mUnlockCameraHandler) {
          mUnlockCameraHandler();
        }
      }
      // ...

使用UnlockCameraHandler

  void unlock_handler() { camera->unlock(); }

使用std::recursive_mutex 至少可以解决这个问题,但还有其他问题。

特别是,当低优先级线程拥有锁时,它可以被授予更高优先级线程的锁悄悄地替换 - 而低优先级线程永远不会知道。它可能会继续使用“受保护”设备,因为没有人告诉它不要这样做。这种设计似乎不太可能以您想要的方式保护设备。

【讨论】:

  • 您建议使用什么来自动向低优先级线程发出信号,告知它应该在高优先级线程可以访问资源之前解锁该资源?
  • 我不会,说实话 - 处理好抢占是很困难的。如果必须这样做,将同步集成到设备周围的一些包装器中会更容易,因此设备访问调用可以每次检查调用线程是否仍然允许。
猜你喜欢
  • 1970-01-01
  • 2014-03-12
  • 2022-01-25
  • 2012-10-17
  • 2015-12-22
  • 2021-05-24
  • 2023-03-13
  • 1970-01-01
  • 2018-03-01
相关资源
最近更新 更多