std :: vector <t>和基本的多线程设计模式</t>

时间:2014-01-16 20:34:12

标签: c++ multithreading boost vector

我有我的主要类:Foo,带有一个名为

的变量
 std::vector<Something*> X; 

和功能

 SideThreadUpdate();
 MainThreadUpdate();

如果我的for中有SideThreadUpdate个循环:

for(int i = 0; i < X.size() ; i++)
{
    X->randomBool = true;
}

和我MainThreadUpdate中的一个:

X.push_back(new Something());

这会给我访问违规和其他奇怪的错误。我如何使这个线程安全?

我需要在两个线程中访问变量X,并使用boost::thread进行多线程处理。

我似乎无法在boost::mutex

上制作std::vector<Something*> X;

此外,我需要连续访问变量,并且不能让MainThread或SideThread等待..

我的用例的简化示例;我有1个线程,我想检查冲突,我的其他线程来处理输入和其他东西,都需要访问Xl

3 个答案:

答案 0 :(得分:5)

您需要在向量的访问周围放置一个互斥锁。每当有人试图访问向量时,它应首先锁定互斥锁,进行访问,然后释放互斥锁。这必须在您的main和您的主题中完成。

您可以找到提升同步here的示例。

答案 1 :(得分:3)

最直接的方法 - 因此,如果不是实现级别,最容易在概念级别上获得 - 是通过某种同步原语(例如互斥体)传递对vector的所有访问权限

有很多方法可以做到这一点。最简单的可能最天真的方法是简单地为每个操作锁定互斥锁:

boost::mutex mtx;
std::vector <Foo> foos;

void ThreadFunction()
{
  boost::mutex::scoped_lock lock (mtx); 
// we have exclusive access now

  foos.find (...);  
  foos.push_back (Foo (...));
}

这种方法存在两个潜在但主要的问题。首先,它序列化对vector的访问。也就是说,如果使用vector或在vector上执行任何操作,则在完成该工作之前,其他任何线程都无法使用它。如果从向量中读取2个线程,则一次只能使用1个线程。第二个线程必须等待第一个线程放弃锁定才能继续。也许这正是你想要的行为。你必须决定。

即使是真的,也存在第二个主要问题,那就是为了使其正确,您必须指望程序员确保互斥锁每次都被锁定。这是一个关键的代码行,如果编译器不强迫你这么容易忘记。如果你有多个互斥锁,它们也必须以相同的顺序锁定,但这是另一个故事,与此相关。

通过将编译器用作防止不安全访问的防火墙,您可以修复第二个问题 - 在我看来,更大的问题。如果您尝试使用namespace MT { template <typename Object> class LockingObject { public: LockingObject () { InitializeSRWLock (&mSrwLock); } virtual ~LockingObject () { } Object& GetExclusive () { AcquireSRWLockExclusive (&mSrwLock); return mObject; } void ReleaseExclusive () { ReleaseSRWLockExclusive (&mSrwLock); } Object const& GetShared () { AcquireSRWLockShared (&mSrwLock); return mObject; } void ReleaseShared () { ReleaseSRWLockShared (&mSrwLock); } private: SRWLOCK mSrwLock; Object mObject; }; enum LockMode { LM_Exclusive, LM_Shared }; template <LockMode, typename Object> class ObjectAutoLock; template<class Object> class ObjectAutoLock <LM_Exclusive, Object> { public: ObjectAutoLock (LockingObject <Object>& lockingObject) : mLock (lockingObject), mObj (mLock.GetExclusive()) { } virtual ~ObjectAutoLock() { mLock.ReleaseExclusive(); } // Access to the controlled map Object& get() const { return mObj; } operator Object& () const { return get(); } Object* operator-> () const { return &get(); } private: LockingObject<Object>& mLock; Object& mObj; }; template<class Object> class ObjectAutoLock <LM_Shared, Object> { public: ObjectAutoLock (LockingObject <Object>& lockingObject) : mLock (lockingObject), mObj (mLock.GetShared()) { } virtual ~ObjectAutoLock() { mLock.ReleaseShared(); } // Access to the controlled map const Object& get() const { return mObj; } operator const Object& () const { return get(); } Object const* operator-> () const { return &get(); } private: LockingObject<Object>& mLock; Object const& mObj; }; } 而不先将其锁定,则让编译器发出编译器错误并重新编译。这是一个这样做的类。这也是使用Windows Slim Reader/Writer Lock实现的,它以特定于Windows的方式解决了第一个问题--albiet - 它提供了对多个线程的并发共享访问,但只能访问一个线程。您可以采用此代码来满足您的需求:

LockingObject

vector <foo>是这里的主要类。如果您需要保护LockingObject <vector <Foo>>,那么您将实例化为LockingObject以包含该privatevectorvector提供了LockingObject成员变量,因此对该ObjectAutoLock的任何访问都必须通过LockingObject的访问者方法。这些访问方法只需锁定&amp;解锁同步对象;读者会返回对它的引用。

作为此类的客户端,您将主要与ObjectAutoLock对象(一个RAII构造)进行交互。通过将对vector的引用传递给它的构造函数,构造其中一个“在堆栈上”。然后,您可以使用ObjectAutoLock as-if 它实际上是ObjectAutoLock,因此vector会让您失去理智。完成public之后,只需让它从堆栈中掉落,LockingObject上的锁定就会被释放。

我让private访问ObjectAutoLock访问者方法,因为这在我的特定用法中更有意义。您可以通过typedef std::vector <Foo> Foos; typedef MT::LockingObject <Foos> LockedFoos; LockedFoos lockedFoos; typedef MT::ObjectAutoLock <MT::LM_Shared> SharedFoos; typedef MT::ObjectAutoLock <MT::LM_Exclusive> ExclusiveFoos; void ReaderThread() { SharedFoos foos (lockedFoos); // doesnt return until lock acquired // we have shared access now. nobody has exclusive access foos.find (...); // foos can be used like it's a vector now } void WriterThread() { ExclusiveFoos foos (lockedFoos); // doesnt return until lock acquired // we have exclusive access now. nobody else has any access foos.push_back (Foo (...)); } 使这些更安全,并使vector成为朋友。

按照设计,此代码将使用如下:

{{1}}

这里整体设计的主要好处是它可以很容易地正确使用它,很难错误地使用它。您正在使用编译器作为防止不安全访问的防火墙。您无法直接访问{{1}}而无需通过锁定,这是一个RAII结构,因此它将被正确释放。如果你试图直接找到向量,它将引起编译器错误。精简的读写器内容可以与您的应用程序中的任何锁定交换出来。

答案 2 :(得分:2)

这是一个小模式。使用C ++ 11 std库特性(但是boost应该具有相同的功能)。

std::mutex my_mutex;
std::vector<std::unique_ptr<Something>> X; 

void SideThreadUpdate()
{
  std::lock<std::mutex> lock(my_mutex);  // use the same mutex here and
  if(X.size()) X.front()->do_something();
}

void MainThreadUpdate()
{
  std::lock<std::mutex> lock(my_mutex);  // here.
  X.push_back(new Something());
}

构造std::lock<>获取std::mutex(或阻止它成功获取互斥锁之前),并在销毁时释放互斥锁。这样,即使在某处抛出异常,也会自动释放互斥锁。但是,您必须不要尝试以递归方式锁定互斥锁(在同一线程上)。