我有一个“Device”类,表示外围硬件设备的连接。客户端在每个Device对象上调用许多成员函数(“设备函数”)。
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()
调用对于组中的所有函数都是“隐式的”。
换句话说:以类似的方式,派生类可以隐式调用基类构造函数中的公共代码,我想做一些与函数调用(而不是类继承)类似的东西。
有什么建议?
首先,如果在执行任何其他操作之前必须锁定互斥锁,那么您应该调用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_);
获得并持有你的锁。这也将在从块退出时自动释放锁定。
除此之外,请注意你在这里做的事情,很可能,一般来说不是一个好主意。真正的问题是:为什么有这么多不同的方法都需要用同一个互斥锁开始保护?你真的必须支持任意数量的线程吗?你可以随意在任意时间以任意顺序对同一个设备对象进行任意操作吗?如果没有,那你为什么要构建你的Device
抽象来支持这个用例?是否真的没有更好的界面可以为您的应用程序场景设计,了解线程应该在做什么。你真的必须做这么细粒度的锁定吗?考虑当前抽象的效率是多么低效,例如,连续调用多个设备函数,因为这需要不断锁定和解锁,并在整个地方一次又一次地锁定和解锁这个互斥锁......
总而言之,可能有一种方法可以提高锁定频率,同时解决您的原始问题:
我想知道是否有一个C ++构造或设计模式或范例可以用来“组合”这些函数,使得
mutex_.try_lock()
调用对于组中的所有函数都是“隐式的”。
您可以通过将它们直接暴露为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
的特定任务的功能),你也可以永远忘记解锁互斥锁......
然而,即便如此,最重要的是不只是构建一个通用的“线程安全的Device
”。这些东西通常既不高效也不实用。构建一个抽象,反映多个线程在您的特定应用程序中使用Device
进行协作的方式。其他一切都是第二位的。但是在不知道你的应用程序究竟是什么的情况下,实际上并没有什么可以说的......