C++ 如何使应用程序线程安全?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/5125241/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
how to make an application thread safe?
提问by ashmish2
I thought thread safe, in particular, means it must satisfy the need for multiple threads to access the same shared data. But, it seems this definition is not enough.
我认为线程安全,特别是意味着它必须满足多个线程访问相同共享数据的需要。但是,这个定义似乎还不够。
Can anyone please list out the things to be done or taken care of to make an application thread safe. If possible, give an answer with respect to C/C++ language.
任何人都可以列出为了使应用程序线程安全而需要完成或处理的事情。如果可能,请给出有关 C/C++ 语言的答案。
采纳答案by Tim
There are several ways in which a function can be thread safe.
有几种方法可以使函数成为线程安全的。
It can be reentrant. This means that a function has no state, and does not touch any global or static variables, so it can be called from multiple threads simultaneously. The term comes from allowing one thread to enter the function while another thread is already inside it.
它可以是可重入的。这意味着函数没有状态,也不会触及任何全局或静态变量,因此可以从多个线程同时调用它。该术语来自允许一个线程进入该函数,而另一个线程已经在其中。
It can have a critical section. This term gets thrown around a lot, but frankly I prefer critical data. A critical section occurs any time your code touches data that is shared across multiple threads. So I prefer to put the focus on that critical data.
它可以有一个临界区。这个术语被广泛使用,但坦率地说,我更喜欢关键数据。每当您的代码涉及跨多个线程共享的数据时,就会发生临界区。所以我更愿意把重点放在关键数据上。
If you use a mutexproperly, you can synchronize access to the critical data, properly protecting from thread unsafe modifications. Mutexes and Locks are very useful, but with great power comes great responsibility. You must not lock the same mutex twice within the same thread (that is a self-deadlock). You must be careful if you acquire more than one mutex, as it increases your risk for deadlock. You must consistently protect your data with mutexes.
如果您正确使用互斥锁,您可以同步对关键数据的访问,正确防止线程不安全的修改。互斥锁和锁非常有用,但能力越大责任越大。您不能在同一个线程中两次锁定同一个互斥锁(即自死锁)。如果您获得多个互斥锁,您必须小心,因为这会增加死锁的风险。您必须始终使用互斥锁保护您的数据。
If all of your functions are thread safe, and all of your shared data properly protected, your application should be thread safe.
如果您的所有函数都是线程安全的,并且所有共享数据都受到适当保护,那么您的应用程序应该是线程安全的。
As Crazy Eddie said, this is a huge subject. I recommend reading up on boost threads, and using them accordingly.
正如 Crazy Eddie 所说,这是一个巨大的主题。我建议阅读 boost 线程,并相应地使用它们。
low-level caveat: compilers can reorder statements, which can break thread safety. With multiple cores, each core has its own cache, and you need to properly sync the caches to have thread safety. Also, even if the compiler doesn't reorder statements, the hardware might. So, full, guaranteed thread safety isn't actually possible today. You can get 99.99% of the way there though, and work is being done with compiler vendors and cpu makers to fix this lingering caveat.
低级警告:编译器可以重新排序语句,这会破坏线程安全。对于多个内核,每个内核都有自己的缓存,您需要正确同步缓存以确保线程安全。此外,即使编译器不重新排序语句,硬件也可能会。因此,完全的、有保证的线程安全在今天实际上是不可能的。不过,您可以获得 99.99% 的结果,并且编译器供应商和 CPU 制造商正在努力解决这个挥之不去的警告。
Anyway, if you're looking for a checklist to make a class thread-safe:
无论如何,如果您正在寻找使类线程安全的清单:
- Identify any data that is shared across threads (if you miss it, you can't protect it)
- create a member
boost::mutex m_mutex
and use it whenever you try to access that shared member data (ideally the shared data is private to the class, so you can be more certain that you're protecting it properly). - clean up globals. Globals are bad anyways, and good luck trying to do anything thread-safe with globals.
- Beware the
static
keyword. It's actually not thread safe. So if you're trying to do a singleton, it won't work right. - Beware the Double-Checked Lock Paradigm. Most people who use it get it wrong in some subtle ways, and it's prone to breakage by the low-level caveat.
- 识别跨线程共享的任何数据(如果错过,则无法保护它)
- 创建一个成员
boost::mutex m_mutex
并在您尝试访问该共享成员数据时使用它(理想情况下,共享数据是该类的私有数据,因此您可以更加确定您正在正确保护它)。 - 清理全局变量。无论如何,全局变量都是不好的,祝你好运,试图用全局变量做任何线程安全的事情。
- 注意
static
关键词。它实际上不是线程安全的。因此,如果您尝试进行单例操作,它将无法正常工作。 - 当心双重检查锁范式。大多数使用它的人都会以一些微妙的方式弄错,并且很容易被低级警告破坏。
That's an incomplete checklist. I'll add more if I think of it, but hopefully it's enough to get you started.
这是一个不完整的清单。如果我想到它,我会添加更多,但希望它足以让你开始。
回答by Theo
Two things:
两件事情:
1. Make sure you use no globals. If you currently have globals, make them members of a per-thread state struct and then have the thread pass the struct to the common functions.
1. 确保你没有使用全局变量。如果您当前有全局变量,请将它们设为每个线程状态结构的成员,然后让线程将该结构传递给公共函数。
For example if we start with:
例如,如果我们开始:
// 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;
}
Once we add in a state struct the code becomes:
一旦我们添加了一个状态结构,代码就变成了:
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);
}
Now you may ask why not just pass x and y in as parameters. The reason is that this example is a simplification. In real life your state struct may have 20 fields and passing most of these parameters 4-5 functions down becomes daunting. You'd rather pass one parameter instead of many.
现在您可能会问为什么不直接将 x 和 y 作为参数传入。原因是这个例子是一个简化。在现实生活中,您的状态结构可能有 20 个字段,将这些参数中的大部分传递给 4-5 个函数变得令人生畏。您宁愿传递一个参数而不是多个参数。
2. If your threads have data in common that needs to be shared, then you need to look into critical sections and semaphores. Every time one of your threads accesses the data, it needs to block the other threads and then unblock them when it's done accessing the shared data.
2. 如果您的线程有需要共享的公共数据,那么您需要查看临界区和信号量。每次您的一个线程访问数据时,它都需要阻塞其他线程,然后在完成访问共享数据后解除对它们的阻塞。
回答by GutiMac
If you want to make a exclusive access to the class' methods you have to use a lock at these functions.
如果你想对类的方法进行独占访问,你必须在这些函数上使用锁。
The different type of locks:
不同类型的锁:
Using atomic_flg_lck:
使用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;
};
Using atomic:
使用原子:
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;
};
Using mutex:
使用互斥:
class SLock
{
public:
void lock()
{
lck.lock();
}
void unlock()
{
lck.unlock();
}
private:
std::mutex lck;
};
Just for Windows:
仅适用于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;
};
The atomicand and atomic_flagkeep the thread in a spin count. Mutexjust sleeps the thread. If the wait time is too long maybe is better sleep the thread. The last one "CRITICAL_SECTION" keeps the thread in a spin count until a time is consumed, then the thread goes to sleep.
在原子和和atomic_flag保持螺纹旋转计数。互斥只是让线程休眠。如果等待时间太长,也许最好让线程休眠。最后一个“ CRITICAL_SECTION”使线程保持自旋计数,直到消耗时间,然后线程进入睡眠状态。
How to use these critical sections?
如何使用这些临界区?
unique_ptr<SLock> raiilock(new SLock());
class Smartlock{
public:
Smartlock(){ raiilock->lock(); }
~Smartlock(){ raiilock->unlock(); }
};
Using the raii idiom. The constructor to lock the critical section and the destructor to unlock it.
使用 raii 习语。锁定临界区的构造函数和解锁临界区的析构函数。
Example
例子
class MyClass {
void syncronithedFunction(){
Smartlock lock;
//.....
}
}
This implementation is thread safe and exception safe because the variable lock is saved in the stack so when the function scope is ended (end of function or an exception) the destructor will be called.
此实现是线程安全和异常安全的,因为变量锁保存在堆栈中,因此当函数作用域结束(函数结束或异常)时,将调用析构函数。
I hope that you find this helpful.
我希望你觉得这很有帮助。
Thanks!!
谢谢!!
回答by Lalaland
One idea is to think of your program as a bunch of threads commutating through queues. Each thread would have one queue, and these queues would be shared (along with a shared data synchronization method(such as a mutex, etc) ) to all of the threads.
一个想法是将您的程序视为一堆通过队列交换的线程。每个线程将有一个队列,这些队列将与所有线程共享(以及共享数据同步方法(例如互斥锁等))。
Then "solve" the producer/consumer problem however you want to keep the queues from underflowing or overflowing. http://en.wikipedia.org/wiki/Producer-consumer_problem
然后“解决”生产者/消费者问题,但是您希望防止队列下溢或溢出。http://en.wikipedia.org/wiki/Producer-consumer_problem
As long as you keep your threads localized, just sharing data with by sending copies over the queue, and not accessing thread unsafe things like (most) gui libraries and static variables in multiple threads, then you should be fine.
只要你保持你的线程本地化,只是通过在队列上发送副本来共享数据,而不是访问线程不安全的东西,比如(大多数)gui 库和多线程中的静态变量,那么你应该没问题。