组成员函数都需要先隐式互斥锁?

Group member functions to all require implicit mutex lock first?

我有一个"Device"class代表一个外围硬件设备的连接。客户端在每个设备对象上调用多个成员函数 ("device functions")。

class Device {
public:
    std::timed_mutex mutex_;

    void DeviceFunction1();
    void DeviceFunction2();
    void DeviceFunction3();
    void DeviceFunction4();
    // void DeviceFunctionXXX();  lots and lots of device functions

    // other stuff 
    // ...
};

设备 class 有一个成员 std::timed_mutex mutex_ 在与设备通信之前必须由每个设备函数锁定,以防止并发线程同时与设备通信。

一个明显但重复且麻烦的方法是copy/paste每个设备功能执行顶部的mutex_.try_lock()代码。

void Device::DeviceFunction1() {
    mutex_.try_lock();        // this is repeated in ALL functions

    // communicate with device
    // other stuff 
    // ...
 }

但是,我想知道是否存在可用于 "group" 这些函数的 C++ 构造或设计模式或范例,使得 mutex_.try_lock() 调用是 "implicit" 用于组中的所有函数。

换句话说:以类似的方式派生 class 可以在基础 class 构造函数中隐式调用公共代码,我想对函数调用做一些类似的事情(而不是class继承)。

有什么建议吗?

首先,如果互斥锁必须在你做任何事情之前被锁定,那么你应该调用mutex_.lock(),或者至少不要忽略try_lock实际上可能无法锁定互斥体。此外,手动调用以锁定和解锁互斥体非常容易出错,而且可能比您想象的要难得多。不要这样做。例如,使用 std::lock_guard 代替。

您使用 std::timed_mutex 的事实表明您的真实代码中实际发生的事情可能涉及更多(否则您会使用 std::timed_mutex 来做什么)。假设你真正在做的事情比仅仅调用 try_lock 并忽略其 return 值更复杂,请考虑将你的复杂锁定过程(无论它是什么)封装在自定义锁保护类型中,例如:

class the_locking_dance
{
    auto do_the_locking_dance(std::timed_mutex& mutex)
    {
        while (!mutex.try_lock_for(100ms))
            /* do whatever it is that you wanna do */;
        return std::lock_guard { mutex, std::adopt_lock_t };
    }

    std::lock_guard<std::timed_mutex> guard;

public:
    the_locking_dance(std::timed_mutex& mutex)
        : guard(do_the_locking_dance(mutex))
    {
    }
};

然后创建局部变量

the_locking_dance guard(mutex_);

获取并持有您的锁。这也会在退出块时自动释放锁。

除此之外,请注意,您在这里所做的一般来说很可能不是一个好主意。真正的问题是:为什么有这么多不同的方法一开始都需要用同一个互斥量来保护?您是否真的必须支持任意数量的您一无所知的线程,这些线程可能会在任意时间以任意顺序任意地对同一设备对象执行任意操作?如果不是,那你为什么要构建 Device 抽象来支持这个用例?真的没有更好的界面可以为您的应用程序场景设计,了解线程实际上应该做什么。您真的必须进行这种细粒度锁定吗?考虑一下你当前的抽象是多么低效,例如,连续调用多个设备功能,因为这需要不断地锁定和解锁以及锁定和解锁这个互斥体,一次又一次地在整个地方......

综上所述,可能有一种方法可以提高锁定频率,同时解决您原来的问题:

I'm wondering if there is a C++ construct or design pattern or paradigm which can be used to "group" these functions in such a way that the mutex_.try_lock() call is "implicit" for all functions in the group.

您可以将这些函数分组,而不是直接将它们公开为 Device 对象的方法,而是公开为另一种锁守卫类型的方法,例如

class Device
{
    …

    void DeviceFunction1();
    void DeviceFunction2();
    void DeviceFunction3();
    void DeviceFunction4();

public:
    class DeviceFunctionSet1
    {
        Device& device;
        the_locking_dance guard;

    public:
        DeviceFunctionSet1(Device& device)
            : device(device), guard(device.mutex_)
        {
        }

        void DeviceFunction1() { device.DeviceFunction1(); }
        void DeviceFunction2() { device.DeviceFunction2(); }
    };

    class DeviceFunctionSet2
    {
        Device& device;
        the_locking_dance guard;

    public:
        DeviceFunctionSet2(Device& device)
            : device(device), guard(device.mutex_)
        {
        }

        void DeviceFunction3() { device.DeviceFunction4(); }
        void DeviceFunction4() { device.DeviceFunction3(); }
    };
};

现在,要在给定的块范围内访问您的设备的方法,您首先获取相应的 DeviceFunctionSet 然后您可以调用这些方法:

{
    DeviceFunctionSet1 dev(my_device);

    dev.DeviceFunction1();
    dev.DeviceFunction2();
}

这样做的好处是整个函数组只发生一次锁定(希望这些函数在逻辑上属于一组函数,用于通过您的 Device 完成特定任务) 自动,你也永远不会忘记解锁互斥锁…

然而,即便如此,最重要的是不要仅仅构建一个通用的 "thread-safe Device"。这些东西通常既没有效率也没有真正用处。在您的特定应用程序 中使用 Device 构建反映多个线程应该合作的方式的抽象。其他一切都是次要的。但是,在不知道您的应用程序实际是什么的情况下,真的没有什么可以说的了……