当一个对象可能被多个线程修改、读取数据时,需要给相应的数据加锁,尤其是堆上的大块数据。锁的来源应该是来自于共享对象,这样才能保证其内部数据同一时间只被一个线程访问。要修改对象的数据时,可以获取锁的引用来加锁,但是这样容易忽略需要加锁的要求,所以设计了一个类,提供统一的接口来使用互斥量,减少失误。
父类:
/*
继承这个类。当子类对象作为(智能)指针在各个线程传递时,使用 LockWork 调用子类的线程不安全函数。
当需要操作子类中非原子的数据时,可以使用lambda、成员函数、全局函数,传入 LockWork 处理。
*/
template<class T>
class Lockable {
private:
std::timed_mutex mutex_;
/*
std::timed_mutex 本身不可拷贝,为了明显,把Lockable也声明为不可拷贝
*/
Lockable(const Lockable&) = delete;
Lockable& operator=(const Lockable&) = delete;
public:
Lockable() = default;
/// @brief 当需要操作 T 内部非原子类型数据时,确保通过 LockWork 执行
/// @tparam F 操作函数类型
/// @tparam ...Args 参数类型
/// @param f 操作函数
/// @param ...args 参数列表
template <class F, class... Args>
bool LockWork(F&& f, Args &&... args) {
std::lock_guard<std::timed_mutex> lck(mutex_);
auto func = std::bind(std::forward<F>(f), std::forward<Args>(args)...);
func();
return true;
}
};
实现一个子类:
class Foo:public Lockable<Foo>{
public:
char data[10];
~Foo() {
std::cout << " ~Foo\n";
}
void show(int a,int b,int c) {
std::cout << "show\n";
}
};
两个测试函数:
Foo f;
std::atomic<bool> stop = false;
void gfunc(int value) {
auto set = [&]() {
for (int i = 0; i < 10; i++) {
f.data[i] = value;
}
};
while (!stop)
{
f.LockWork(set);
//Sleep(10);
}
}
void gFunc_cal() {
auto calcu = [&]() {
int sum = 0;
for (int i = 0; i < 10; i++) {
sum += f.data[i];
}
if (sum && sum != 10 && sum != 20) {
printf_s("Error");
}
else {
printf_s("Sum is %d\n",sum);
}
};
while (!stop) {
f.LockWork(calcu);
}
}
main 函数:
int main()
{
std::thread th1(gfunc, 1);
std::thread th2(gfunc, 2);
std::thread th3(gFunc_cal);
system("pause");
stop = true;
th1.join();
th2.join();
th3.join();
return 0;
}
测试发现不会输出 Error,证明 th1 和 th2 对 f 的操作是互斥的。