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

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

任何人都可以列出的事情要做或照顾使应用程序线程安全 。 如果可能的话,就C / C ++语言给出一个答案。

函数可以有多种线程安全的方法。

它可以是可重入的 。 这意味着一个函数没有状态,不会触及任何全局variables或静态variables,所以它可以同时从多个线程中调用。 这个术语来自允许一个线程进入该function,而另一个线程已经在其中。

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

如果您正确使用互斥锁 ,则可以同步对关键数据的访问,从而妥善保护线程不安全的修改。 互斥和锁是非常有用的,但是强大的力量是很大的责任。 你不能在同一个线程中两次locking同一个互斥体(这是一个自我死锁)。 如果您获得多个互斥量,则必须小心,因为这会增加您陷入僵局的风险。 您必须始终如一地使用互斥锁来保护您的数据。

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

正如疯狂的艾迪所说,这是一个巨大的课题。 我build议阅读升压线程,并相应地使用它们。

低级警告 :编译器可以重新排列语句,这可以打破线程安全。 使用多个内核,每个内核都有自己的caching,并且需要正确同步caching才能保证线程安全。 另外,即使编译器不重新排列语句,硬件也可能会这样。 所以,充分,保证线程安全是不是今天实际上是可能的。 尽pipe如此,你可以获得99.99%的比例,而编译器厂商和CPU制造商正在努力解决这个徘徊的问题。

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

  • 识别跨线程共享的任何数据(如果您错过了,则无法保护)
  • 创build一个成员boost::mutex m_mutex ,并在你尝试访问共享成员数据时使用它(理想情况下,共享数据对于类是私有的,所以你可以更确定你是否正确保护它)。
  • 清理全局。 Globals反正是坏的,祝你好运,试图做全线程安全的事情。
  • 当心static关键字。 它实际上不是线程安全的。 所以如果你想要做一个单身人士,那么这样做是行不通的。
  • 谨防双重locking范式。 大多数使用它的人会以一些微妙的方式来错误的,而且由于低级警告而容易被破坏。

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

两件事情:

1.确保你不使用全局variables。 如果你现在有全局variables,使它们成为每线程状态结构的成员,然后让线程将结构传递给通用函数。

例如,如果我们从以下开始:

 // 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); } 

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

2.如果您的线程有共同的数据需要共享,那么您需要查看关键部分和信号量。 每次有一个线程访问数据时,都需要阻塞其他线程,然后在访问共享数据时解除阻塞。

如果你想独占访问类的方法,你必须在这些函数上使用锁。

不同types的锁:

使用atomic_flg_lck:

 class SLock { public: void lock() { while (lck.test_and_set(std::memory_order_acquire)); } void unlock() { lck.clear(std::memory_order_release); } SLock(){ //lck = ATOMIC_FLAG_INIT; lck.clear(); } private: std::atomic_flag lck;// = ATOMIC_FLAG_INIT; }; 

使用primefaces:

 class SLock { public: void lock() { while (lck.exchange(true)); } void unlock() { lck = true; } SLock(){ //lck = ATOMIC_FLAG_INIT; lck = false; } private: std::atomic<bool> lck; }; 

使用互斥体:

 class SLock { public: void lock() { lck.lock(); } void unlock() { lck.unlock(); } private: std::mutex lck; }; 

仅适用于Windows

 class SLock { public: void lock() { EnterCriticalSection(&g_crit_sec); } void unlock() { LeaveCriticalSection(&g_crit_sec); } SLock(){ InitializeCriticalSectionAndSpinCount(&g_crit_sec, 0x80000400); } private: CRITICAL_SECTION g_crit_sec; }; 

primefaces和atomic_flag保持线程在一个旋转计数。 互斥体只是睡觉的线程。 如果等待的时间太长,也许是更好的睡眠线程。 最后一个“ CRITICAL_SECTION ”保持线程在旋转计数直到消耗时间,然后线程进入睡眠。

如何使用这些关键部分?

 unique_ptr<SLock> raiilock(new SLock()); class Smartlock{ public: Smartlock(){ raiilock->lock(); } ~Smartlock(){ raiilock->unlock(); } }; 

使用raii成语。 构造函数locking关键部分和析构函数来解锁它。

 class MyClass { void syncronithedFunction(){ Smartlock lock; //..... } } 

这个实现是线程安全和exception安全的,因为variables锁被保存在堆栈中,所以当函数作用域结束时(函数结束或exception)析构函数将被调用。

我希望你觉得这有帮助。

谢谢!!

一个想法是把你的程序想象成一堆线程在队列中换行。 每个线程都有一个队列,这些队列将与所有线程共享(以及一个共享的数据同步方法(如互斥等))。

然后“解决”生产者/消费者问题,但是你想保持队列不被下溢或溢出。 http://en.wikipedia.org/wiki/Producer-consumer_problem

只要你保持你的线程本地化,只是通过在队列中发送拷贝来共享数据,而不是像multithreading中的(大多数)gui库和静态variables那样访问线程不安全的东西,那么你应该没问题。