具有多个成员函数的实例的线程安全锁定



我有一个被多个线程使用的结构体实例。每个线程都包含未知数量的函数调用,这些调用会改变结构体成员变量。

我有一个专门的函数,试图"保留";当前线程的结构实例,我想确保没有其他线程可以保留实例,直到原始线程允许它。

我想到了互斥锁,因为它们可以用来保护资源,但是我只知道std::lock_guard是在一个函数的作用域中,但是没有为锁和解锁之间的所有函数调用添加保护。

当我知道它总是按顺序调用reserve和release时,有可能保护这样的资源吗?

片段解释得更好:

#include <iostream>       // std::cout
#include <thread>         // std::thread
#include <mutex>          // std::mutex

struct information_t {
std::mutex mtx;
int importantValue = 0;
// These should only be callable from the thread that currently holds the mutex
void incrementIt() { importantValue++; }
void decrementIt() { importantValue--; }
void reset() { importantValue = 0; }
} protectedResource; // We only have one instance of this that we need to work with
// Free the resource so other threads can reserve and use it
void release()
{
std::cout << "Result: " << protectedResource.importantValue << 'n';
protectedResource.reset();
protectedResource.mtx.unlock(); // Will this work? Can I guarantee the mtx is locked?
}
// Supposed to make sure no other thread can reserve or use it now anymore!
void reserve()
{ 
protectedResource.mtx.lock();
}
int main()
{
std::thread threads[3];

threads[0] = std::thread([]
{
reserve();
protectedResource.incrementIt();
protectedResource.incrementIt();
release();
});
threads[1] = std::thread([]
{
reserve();
// do nothing
release();
});
threads[2] = std::thread([]
{
reserve();
protectedResource.decrementIt();
release();
});
for (auto& th : threads) th.join();
return 0;
}

我的建议:

一个更好的习语可能是一个监视器,它保持资源的锁定并提供对所有者的访问。为了获取资源,reserve()可以返回这样的监视对象(类似于访问资源内容的代理)。任何对reserve()的竞争访问现在都会阻塞(因为互斥锁被锁定了)。当拥有资源的线程完成时,它只是销毁监视对象,从而解锁资源。(这允许将RAII应用于所有这些,使您的代码安全和可维护。)

我修改了OPs代码来描绘它的样子:

#include <iostream>       // std::cout
#include <thread>         // std::thread
#include <mutex>          // std::mutex
class information_t {
private:
std::mutex mtx;
int importantValue = 0;
public:
class Monitor {
private:
information_t& resource;
std::lock_guard<std::mutex> lock;

friend class information_t; // to allow access to constructor.

private:      
Monitor(information_t& resource):
resource(resource), lock(resource.mtx)
{ }
public:
~Monitor()
{
std::cout << "Result: " << resource.importantValue << 'n';
resource.reset();
}

Monitor(const Monitor&) = delete; // copying prohibited
Monitor& operator=(const Monitor&) = delete; // copy assign prohibited

public:
// exposed resource API for monitor owner:
void incrementIt() { resource.incrementIt(); }
void decrementIt() { resource.decrementIt(); }
void reset() { resource.reset(); }
};
friend class Monitor; // to allow access to private members

public:
Monitor aquire() { return Monitor(*this); }

private:
// These should only be callable from the thread that currently holds the mutex
// Hence, they are private and accessible through a monitor instance only
void incrementIt() { importantValue++; }
void decrementIt() { importantValue--; }
void reset() { importantValue = 0; }
} protectedResource; // We only have one instance of this that we need to work with
#if 0 // OBSOLETE
// Free the resource so other threads can reserve and use it
void release()
{
protectedResource.reset();
protectedResource.mtx.unlock(); // Will this work? Can I guarantee the mtx is locked?
}
#endif // 0
// Supposed to make sure no other thread can reserve or use it now anymore!
information_t::Monitor reserve()
{ 
return protectedResource.aquire();
}
using MyResource = information_t::Monitor;
int main()
{
std::thread threads[3];

threads[0]
= std::thread([]
{
MyResource protectedResource = reserve();
protectedResource.incrementIt();
protectedResource.incrementIt();
// scope end releases protectedResource
});
threads[1]
= std::thread([]
{
try {
MyResource protectedResource = reserve();
throw "Haha!";
protectedResource.incrementIt();
// scope end releases protectedResource
} catch(...) { }
});
threads[2]
= std::thread([]
{
MyResource protectedResource = reserve();
protectedResource.decrementIt();
// scope end releases protectedResource
});
for (auto& th : threads) th.join();
return 0;
}

输出:

Result: 2
Result: -1
Result: 0

coliru Live Demo

当我知道它总是按顺序调用reserve和release时,有可能保护这样的资源吗?

没有必要再担心这个了。正确的用法如下:

  • 要访问资源,需要一个监视器。
  • 如果你得到它,你是资源的唯一所有者。
  • 如果您退出作用域(您将监视器作为局部变量存储在其中),监视器将被销毁,因此锁定的资源将自动释放。

后一种情况甚至会发生在意外的救助中(在MCVE和throw "Haha!";中)。

进一步,我做了以下函数private:

  • information_t::increment()
  • information_t::decrement()
  • information_t::reset()

因此,没有未经授权的访问是可能的。要正确使用它们,必须获取information_t::Monitor实例。它为那些可以在监视器所在的范围内使用的函数提供public包装器,即仅由所有者线程使用。

最新更新