- android - RelativeLayout 背景可绘制重叠内容
- android - 如何链接 cpufeatures lib 以获取 native android 库?
- java - OnItemClickListener 不起作用,但 OnLongItemClickListener 在自定义 ListView 中起作用
- java - Android 文件转字符串
如果按原样使用,下面的代码会导致未定义的行为:
vector<int> vi;
...
vi.push_back(1); // thread-1
...
vi.pop(); // thread-2
传统的方法是用std::mutex
来修复它:
std::lock_guard<std::mutex> lock(some_mutex_specifically_for_vi);
vi.push_back(1);
然而,随着代码的增长,这样的事情开始看起来很麻烦,因为每次在方法之前都会有一个锁。此外,对于每个对象,我们可能必须维护一个互斥体。
在不影响访问对象和声明显式互斥锁的语法的情况下,我想创建一个模板,它可以完成所有样板文件的工作。例如
Concurrent<vector<int>> vi; // specific `vi` mutex is auto declared in this wrapper
...
vi.push_back(1); // thread-1: locks `vi` only until `push_back()` is performed
...
vi.pop () // thread-2: locks `vi` only until `pop()` is performed
在当前的 C++ 中,这是不可能实现的。但是,我尝试了一个代码,如果只是将 vi.
更改为 vi->
,那么事情将按上述代码注释中的预期工作。
// The `Class` member is accessed via `->` instead of `.` operator
// For `const` object, it's assumed only for read purpose; hence no mutex lock
template<class Class,
class Mutex = std::mutex>
class Concurrent : private Class
{
public: using Class::Class;
private: class Safe
{
public: Safe (Concurrent* const this_,
Mutex& rMutex) :
m_This(this_),
m_rMutex(rMutex)
{ m_rMutex.lock(); }
public: ~Safe () { m_rMutex.unlock(); }
public: Class* operator-> () { return m_This; }
public: const Class* operator-> () const { return m_This; }
public: Class& operator* () { return *m_This; }
public: const Class& operator* () const { return *m_This; }
private: Concurrent* const m_This;
private: Mutex& m_rMutex;
};
public: Safe ScopeLocked () { return Safe(this, m_Mutex); }
public: const Class* Unsafe () const { return this; }
public: Safe operator-> () { return ScopeLocked(); }
public: const Class* operator-> () const { return this; }
public: const Class& operator* () const { return *this; }
private: Mutex m_Mutex;
};
operator->()
的函数是否会导致 C++ 中的未定义行为?对于相互依赖的语句,需要更长的锁定时间。因此,引入了一个方法:ScopeLocked()
。这等效于 std::lock_guard()
。然而,给定对象的互斥锁是在内部维护的,所以它在语法上仍然更好。
例如而不是下面有缺陷的设计(如答案中所建议的):
if(vi->size() > 0)
i = vi->front(); // Bad: `vi` can change after `size()` & before `front()`
应该依赖下面的设计:
auto viLocked = vi.ScopeLocked();
if(viLocked->size() > 0)
i = viLocked->front(); // OK; `vi` is locked till the scope of `viLocked`
换句话说,对于相互依赖的语句,应该使用ScopeLocked()
。
最佳答案
不要这样做。
几乎不可能创建一个线程安全的集合类,其中每个方法都需要一个锁。
考虑您提议的 Concurrent 类的以下实例。
Concurrent<vector<int>> vi;
开发人员可能会出现并执行此操作:
int result = 0;
if (vi.size() > 0)
{
result = vi.at(0);
}
另一个线程可能会在第一个线程调用 size()
和 at(0)
之间进行此更改。
vi.clear();
所以现在,同步的操作顺序是:
vi.size() // returns 1
vi.clear() // sets the vector's size back to zero
vi.at(0) // throws exception since size is zero
因此,即使您有一个线程安全的 vector 类,两个竞争线程也可能导致在意想不到的地方抛出异常。
这只是最简单的例子。还有其他方法可以让多个线程同时尝试读/写/迭代可能会无意中破坏您对线程安全的保证。
你提到整个事情的动机是这种模式很麻烦:
vi_mutex.lock();
vi.push_back(1);
vi_mutex.unlock();
事实上,有一些辅助类可以使这个更干净,即 lock_guard,它将使用互斥体来锁定其构造函数并在析构函数中解锁
{
lock_guard<mutex> lck(vi_mutex);
vi.push_back(1);
}
然后其他代码在实践中变成了线程安全的:
{
lock_guard<mutex> lck(vi_mutex);
result = 0;
if (vi.size() > 0)
{
result = vi.at(0);
}
}
更新:
我编写了一个示例程序,使用您的 Concurrent 类来演示导致问题的竞争条件。这是代码:
Concurrent<list<int>> g_list;
void thread1()
{
while (true)
{
if (g_list->size() > 0)
{
int value = g_list->front();
cout << value << endl;
}
}
}
void thread2()
{
int i = 0;
while (true)
{
if (i % 2)
{
g_list->push_back(i);
}
else
{
g_list->clear();
}
i++;
}
}
int main()
{
std::thread t1(thread1);
std::thread t2(thread2);
t1.join(); // run forever
return 0;
}
在未优化的构建中,上面的程序会在几秒钟内崩溃。 (零售有点困难,但错误仍然存在)。
关于c++ - 如何使用自然语法实现线程安全的容器?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54781372/
我是一名优秀的程序员,十分优秀!