如何使应用程序线程安全?

ash*_*sh2 57 c c++ concurrency multithreading thread-safety

我认为线程安全,特别是意味着它必须满足多线程访问相同共享数据的需要.但是,似乎这个定义还不够.

任何人都可以列出要完成或处理的事情,以使应用程序线程安全.如果可能的话,给出一个关于C/C++语言的答案.

Tim*_*Tim 58

有几种方法可以使函数成为线程安全的.

它可以是可重入的.这意味着函数没有状态,并且不接触任何全局变量或静态变量,因此可以同时从多个线程调用它.该术语来自允许一个线程进入该函数而另一个线程已经在其中.

它可以有一个关键部分.这个术语被抛出很多,但坦率地说我更喜欢关键数据.每当您的代码触及跨多个线程共享的数据时,就会出现一个关键部分.所以我更喜欢把重点放在关键数据上.

如果正确使用互斥锁,则可以同步对关键数据的访问,从而正确保护线程不安全的修改.互斥锁和锁是非常有用的,但强大的功能带来了巨大的责任.您不能在同一个线程中两次锁定相同的互斥锁(这是一个自死锁).如果您获得多个互斥锁,则必须小心,因为这会增加死锁的风险.您必须使用互斥锁持续保护数据.

如果所有函数都是线程安全的,并且所有共享数据都受到适当保护,那么您的应用程序应该是线程安全的.

正如Crazy Eddie所说,这是一个很大的主题.我建议阅读boost线程,并相应地使用它们.

低级警告:编译器可以重新排序语句,这可能会破坏线程的安全性.对于多个内核,每个内核都有自己的缓存,您需要正确同步缓存以确保线程安全.此外,即使编译器没有重新排序语句,硬件也可能.因此,今天实际上不可能完全保证线程安全.你可以获得99.99%的方式,并且正在与编译器供应商和cpu制造商一起工作来解决这个挥之不去的警告.

无论如何,如果你正在寻找一个清单来使类线程安全:

  • 识别跨线程共享的任何数据(如果您错过了,则无法保护它)
  • 创建一个成员boost::mutex m_mutex并在您尝试访问该共享成员数据时使用它(理想情况下,共享数据对于该类是私有的,因此您可以更确定您是否正确保护它).
  • 清理全局变量.无论如何,Globals都很糟糕,并试图用全局变量做任何线程安全的运气.
  • 注意static关键字.它实际上不是线程安全的.所以,如果你正在尝试做一个单身,它将无法正常工作.
  • 当心双重锁定范例.大多数使用它的人都会以某种微妙的方式弄错,并且很容易受到低级警告的破坏.

这是一份不完整的清单.如果我想到它,我会添加更多,但希望它足以让你开始.

  • 对你的低级警告的一个很好的引用是Hans Boehm的PLDI 2005论文,"线程不能实现为图书馆":http://portal.acm.org/citation.cfm?id = 1065042,http:// www. hpl.hp.com/techreports/2004/HPL-2004-209.html (2认同)
  • 我很确定这是它:[让C++线程正确](http://www.youtube.com/watch?v=mrvAqvtWYb4).在8:20他进入了一些指令的硬件重新排序,并提到写缓冲区,这可能导致一些奇怪的行为. (2认同)
  • 值得一提的是,在C11和C ++ 11(这些语言的第一个版本中实际上具有定义明确的线程)中,“静态” *是线程安全的。 (2认同)

The*_*heo 14

两件事情:

1.确保不使用全局变量.如果你当前有全局变量,那么让它们成为每线程状态结构的成员,然后让线程将结构传递给公共函数.

例如,如果我们开始:

// 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;
}
Run Code Online (Sandbox Code Playgroud)

一旦我们添加了一个状态结构,代码就会变成:

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);
}
Run Code Online (Sandbox Code Playgroud)

现在您可能会问为什么不将x和y作为参数传递.原因是这个例子是一个简化.在现实生活中,你的状态结构可能有20个字段,并且通过大多数这些参数4-5个函数变得令人生畏.你宁愿传递一个参数而不是许多参数.

2.如果您的线程有共同的数据需要共享,那么您需要查看关键部分和信号量.每当你的一个线程访问数据时,它就需要阻塞其他线程,然后在访问共享数据时取消阻塞它们.