我有一个代表外围硬件设备连接的“设备”类。客户端在每个设备对象上调用许多成员函数(“设备函数”)。
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
// ...
};
Device 类有一个成员 std::timed_mutex mutex_
,它必须在与设备通信之前由每个设备函数锁定,以防止并发线程同时与设备通信。
一个明显但重复且麻烦的方法是在每个设备功能的执行顶部复制/粘贴 mutex_.try_lock()
代码。
void Device::DeviceFunction1() {
mutex_.try_lock(); // this is repeated in ALL functions
// communicate with device
// other stuff
// ...
}
但是,我想知道是否有 C++ 构造或设计模式或范例可用于以 mutex_.try_lock()
调用的方式“分组”这些函数组中所有函数的“隐式”。
换句话说:以类似于派生类可以在基类构造函数中隐式调用公共(public)代码的方式,我想用函数调用做一些类似的事情(而不是类继承)。
有什么建议吗?
最佳答案
首先,如果互斥锁必须在你做任何事情之前被锁定,那么你应该调用mutex_.lock()
,或者至少不要忽略这个事实try_lock
实际上可能无法锁定互斥体。此外,手动调用以锁定和解锁互斥体非常容易出错,而且可能比您想象的要难得多。不要这样做。使用,例如 std::lock_guard
相反。
您正在使用 std::timed_mutex
的事实表明您的真实代码中实际发生的事情可能涉及更多(您将使用 std 的目的是什么::timed_mutex
否则)。假设您真正在做的事情比仅仅调用 try_lock
并忽略其返回值更复杂,请考虑将您的复杂锁定过程(无论它是什么)封装在自定义锁保护类型中,例如:
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_);
获取并持有你的锁。这也会在退出 block 时自动释放锁。
除此之外,请注意,您在此处所做的一般来说很可能不是一个好主意。真正的问题是:为什么有这么多不同的方法一开始都需要用同一个互斥量来保护?您是否真的必须支持任意数量的您一无所知的线程,这些线程可能会在任意时间以任意顺序任意地对同一设备对象执行任意操作?如果不是,那你为什么要构建你的 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(); }
};
};
现在,要在给定的 block 范围内访问您的设备的方法,您首先获取相应的 DeviceFunctionSet
,然后您可以调用这些方法:
{
DeviceFunctionSet1 dev(my_device);
dev.DeviceFunction1();
dev.DeviceFunction2();
}
这样做的好处是,锁定对整个函数组只发生一次(希望它们在逻辑上属于一组函数,用于通过您的设备
完成特定任务>) 自动,你也永远不会忘记解锁互斥体......
然而,即便如此,最重要的是不要仅仅构建一个通用的“线程安全的Device
”。这些东西通常既没有效率也没有真正用处。构建一个抽象,反射(reflect)多个线程应该使用 Device
在您的特定应用程序中合作的方式。其他一切都是次要的。但是在不知道您的应用程序实际是什么的情况下,真的没有什么可以说的了……
关于c++ - 组成员函数都需要先隐式互斥锁?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55800530/