// Globals
int x;
int y;
// Function that needs to be accessed by multiple threads
// currently relies on globals, and hence cannot work with
// multiple threads
int myFunc()
{
return x+y;
}
一旦我们添加了一个状态结构,代码就会变成:
typedef struct myState
{
int x;
int y;
} myState;
// Function that needs to be accessed by multiple threads
// now takes state struct
int myFunc(struct myState *state)
{
return (state->x + state->y);
}
4 回答
有几种方法可以使函数成为线程安全的 .
它可以是 reentrant . 这意味着函数没有状态,并且不接触任何全局变量或静态变量,因此可以同时从多个线程调用它 . 该术语来自允许一个线程进入该函数而另一个线程已经在其中 .
它可以有 critical section . 这个词被抛出很多,但坦率地说我更喜欢 critical data . 每当您的代码触及跨多个线程共享的数据时,就会出现一个关键部分 . 所以我更喜欢把重点放在关键数据上 .
如果正确使用mutex,则可以同步对关键数据的访问,从而正确防止线程不安全的修改 . 互斥锁和锁是非常有用的,但强大的功能带来了巨大的责任 . 您不能在同一个线程中两次锁定相同的互斥锁(这是一个自死锁) . 如果您获得多个互斥锁,则必须小心,因为这会增加死锁的风险 . 您必须使用互斥锁持续保护数据 .
如果所有函数都是线程安全的,并且所有共享数据都受到适当保护,那么您的应用程序应该是线程安全的 .
正如Crazy Eddie所说,这是一个很大的主题 . 我建议阅读boost线程,并相应地使用它们 .
low-level caveat :编译器可以重新排序语句,这可能会破坏线程安全性 . 对于多个内核,每个内核都有自己的缓存,您需要正确同步缓存以确保线程安全 . 而且,即使编译器今天实际上没有实际可行 . 你可以获得99.99%的方式,并且编译器供应商和cpu制造商正在努力解决这个挥之不去的警告 .
无论如何,如果你正在寻找一个清单来使类线程安全:
识别跨线程共享的任何数据(如果您错过了,则无法保护它)
创建一个成员
boost::mutex m_mutex
并在您尝试访问该共享成员数据时使用它(理想情况下,共享数据对于该类是私有的,因此您可以更确定您是否正确保护它) .清理全局变量 . 不管怎样,Globals都很糟糕,并试图用全局变量做任何线程安全的运气 .
注意
static
关键字 . 它's actually not thread safe. So if you'试图做一个单身,它将无法正常工作 .当心双重锁定范例 . 大多数使用它的人都会以某种微妙的方式弄错,并且很容易受到低级警告的破坏 .
这是一份不完整的清单 . 如果我想到它,我会添加更多,但希望它足以让你开始 .
两件事情:
1.确保不使用全局变量 . 如果你当前有全局变量,那么让它们成为每线程状态结构的成员,然后让线程将结构传递给公共函数 .
例如,如果我们开始:
一旦我们添加了一个状态结构,代码就会变成:
现在您可能会问为什么不将x和y作为参数传递 . 原因是这个例子是一个简化 . 在现实生活中,你的状态结构可能有20个字段,并且通过大多数这些参数4-5个函数变得令人生畏 . 你宁愿传递一个参数而不是许多参数 .
2.如果您的线程有共同的数据需要共享,那么您需要查看关键部分和信号量 . 每当你的一个线程访问数据时,它就需要阻塞其他线程,然后在访问共享数据时取消阻塞它们 .
如果要对类的方法进行独占访问,则必须在这些函数中使用锁 .
不同类型的锁:
使用 atomic_flg_lck:
使用 atomic:
使用 mutex:
仅为 Windows :
atomic 和 and atomic_flag 将线程保持在旋转计数中 . Mutex 只是睡觉了 . 如果等待时间太长也许最好睡眠线程 . 最后一个“ CRITICAL_SECTION ”使线程保持旋转计数直到消耗时间,然后线程进入休眠状态 .
如何使用这些关键部分?
使用raii成语 . 用于锁定关键部分的构造函数和用于解锁它的析构函数 .
例
这个实现是线程安全和异常安全的,因为变量锁保存在堆栈中,所以当函数作用域结束时(函数结束或异常),将调用析构函数 .
我希望你觉得这很有帮助 .
谢谢!!
一个想法是将您的程序视为一系列通过队列进行换向的线程 . 每个线程都有一个队列,这些队列将与所有线程共享(以及共享数据同步方法(如互斥等)) .
然后"solve" 生产环境 者/消费者问题,但是你想让队列保持下溢或溢出 . http://en.wikipedia.org/wiki/Producer-consumer_problem
只要你保持你的线程本地化,只是通过队列发送副本来共享数据,而不是访问多线程中的(大多数)gui库和静态变量等线程不安全的东西,那么你应该没问题 .