我有我的主要类: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
答案 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
以包含该private
。 vector
为vector
提供了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
(或阻止它成功获取互斥锁之前),并在销毁时释放互斥锁。这样,即使在某处抛出异常,也会自动释放互斥锁。但是,您必须不要尝试以递归方式锁定互斥锁(在同一线程上)。