tlock<>:适用于任何 C++ 对象的线程安全读/写提供程序





5.00/5 (34投票s)
一个易于使用的锁类,用于共享/排他互斥函数,支持升级/降级
- GitHub 项目:https://github.com/WindowsNT/tlock
引言
有时,您需要一个对象可以从多个线程访问,可以是只读的(因此所有线程都可以访问),也可以是写的(因此只有单个线程可以访问)。 这称为对象上的共享或排他访问。
该实现基于
现在具有升级和降级机制。 github 仓库包含 rw.hpp(您需要的一切)和一个示例 GUI 项目,该项目使用多个线程绘制一个框。
Using the Code
您需要一个代理类,因此当调用对象的某个方法时,锁定/解锁机制将自动编译。
class proxy
{
private:
T *const p;
RWMUTEX* m;
HANDLE lm = 0;
int me;
public:
proxy(T * const _p, RWMUTEX* _m, int _me) : p(_p), m(_m),
me(_me) { if (me == 2) m->LockWrite(); else lm = m->LockRead(); }
~proxy() { if (me == 2) m->ReleaseWrite(); else { m->ReleaseRead(lm); lm = 0;} }
T* operator -> () { return p; }
const T* operator -> () const { return p; }
T* getp() { return p; }
const T* getpc() const { return p; }
};
这个类的构造函数和析构函数完成所有工作。 它们在调用 object
方法之前使用 RWMutex
进行锁定,并在方法调用之后进行解锁。
tlock
类将如下所示
template <typename T> class tlock
{
private:
mutable T t;
mutable RWMUTEX m;
class proxy
{
T *const p;
RWMUTEX* m;
HANDLE lm = 0;
int me;
public:
proxy(T * const _p, RWMUTEX* _m, int _me) : p(_p), m(_m), me(_me)
{
if (me == 2)
m->LockWrite();
else lm = m->LockRead();
}
~proxy()
{
if (me == 2)
m->ReleaseWrite();
else
{
m->ReleaseRead(lm);
lm = 0;
}
}
T* operator -> () { return p; }
const T* operator -> () const { return p; }
T* getp() { return p; }
const T* getpc() const { return p; }
void upgrade()
{
if (me == 1)
{
lm = 0;
m->Upgrade();
me = 2;
}
}
void downgrade()
{
if (me == 2)
{
lm = m->Downgrade();
me = 1;
}
}
};
public:
template< typename ...Args>
tlock(Args ... args) : t(args...) {}
const proxy r() const
{
return proxy(&t, &m, 1);
}
proxy w()
{
return proxy(&t, &m, 2);
}
T& direct()
{
return t;
}
const T& direct() const
{
return t;
}
void readlock(std::function<void(const T&)> f) const
{
proxy mx(&t, &m, 1);
f(*mx.getp());
}
void writelock(std::function<void(T&)> f)
{
proxy mx(&t, &m, 2);
f(*mx.getp());
}
void rwlock(std::function<void(const T&,std::function<void(std::function<void(T&)>)>)> f)
{
proxy mx(&t, &m, 1);
auto upfunc = [&](std::function<void(T&)> f2)
{
mx.upgrade();
f2(*mx.getp());
mx.downgrade();
};
f(*mx.getp(), upfunc);
}
proxy operator -> () { return w(); }
const proxy operator -> () const { return r(); }
};
当您想要只读访问对象时,调用 r()
方法。 当在 const
对象上调用运算符 ->
时,这是默认行为。
当您想要写入访问对象时,调用 w()
方法。 如果对象不是常量,则这是运算符 ->
的默认行为。
当您想要在锁定的只读对象中执行多个操作时,调用 readlock()
方法,因此它会调用您的函数,传递对常量、锁定的对象的引用。
当您想要在锁定的读写对象中执行多个操作时,调用 writelock()
方法,因此它会调用您的函数,传递对锁定的对象的引用。
当您想要主要进行读取操作,但偶尔需要写入升级时,调用 rwlock()
方法。 它传递对锁定对象的引用以及用于升级锁的升级函数。
让我们看看一些不正确的用法(没有 tlock
)
vector<int> s;
std::thread t1([&]() { s.push_back(0); });
std::thread t2([&]() { s.push_back(1); });
std::thread t3([&]() { s.push_back(2); });
std::thread t4([&]() { s.push_back(3); });
std::thread t5([&]() { s.push_back(4); });
t1.join();t2.join(); t3.join(); t4.join(); t5.join();
嘭!
现在,正确的用法
tlock<vector<int>> s;
std::thread t1([&]() { s->push_back(0); });
std::thread t2([&]() { s->push_back(1); });
std::thread t3([&]() { s->push_back(2); });
std::thread t4([&]() { s->push_back(3); });
std::thread t5([&]() { s->push_back(4); });
t1.join();t2.join(); t3.join(); t4.join(); t5.join();
现在写入是线程安全的。
使用 writelock()
会像这样
s.writelock([&](vector<int>& ss)
{
ss.push_back(100);
ss.push_back(150);
ss.erase(ss.begin());
// Safe operations, s is locked while in this function.
})
以及 rwlock()
的一个示例用法
s.rwlock([&](const vector<int>& vv,
std::function<void(std::function<void(vector<int>&)>)> upgrfunc)
{
// vv read access
upgrfunc([&](vector<int>& nn)
{
// nn write access
// function end downgrades
});
});
tlock2 (C++ 17, shared_mutex)
template <typename T> class tlock2
{
private:
mutable T t;
mutable std::shared_mutex m;
class proxy
{
T* const p;
std::shared_mutex* m;
int me;
public:
proxy(T* const _p, std::shared_mutex* _m, int _me) : p(_p), m(_m), me(_me)
{
if (me == 2)
m->lock();
else
m->lock_shared();
}
~proxy()
{
if (me == 2)
m->unlock();
else
m->unlock_shared();
}
T* operator -> () { return p; }
const T* operator -> () const { return p; }
T* getp() { return p; }
const T* getpc() const { return p; }
};
public:
template< typename ...Args>
tlock2(Args ... args) : t(args...) {}
const proxy r() const
{
return proxy(&t, &m, 1);
}
proxy w()
{
return proxy(&t, &m, 2);
}
std::shared_mutex& mut() { return m; }
T& direct()
{
return t;
}
const T& direct() const
{
return t;
}
void readlock(::std::function<void(const T&)> f) const
{
proxy mx(&t, &m, 1);
f(*mx.getp());
}
void writelock(::std::function<void(T&)> f)
{
proxy mx(&t, &m, 2);
f(*mx.getp());
}
proxy operator -> () { return w(); }
const proxy operator -> () const { return r(); }
};
这使用 std::shared_mutex
。 与 tlock
的区别在于,它无法将读取互斥锁升级为写入互斥锁。
历史
- 2018 年 12 月 13 日:基于
RWMUTEX
进行更新,用于可升级/可降级锁,还包括直接访问和升级函数 - 2017 年 12 月 12 日:基于
RWMUTEX
更新 - 2017 年 8 月 30 日:添加了
readlock
和writelock
便利函数 - 2017 年 5 月 12 日:首次发布