简体   繁体   English

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

[英]how to make an application thread safe?

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 ++语言的答案。

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 mutex properly, 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. 正如Crazy Eddie所说,这是一个很大的主题。 I recommend reading up on boost threads, and using them accordingly. 我建议阅读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). 创建一个成员boost::mutex m_mutex并在您尝试访问该共享成员数据时使用它(理想情况下,共享数据对于该类是私有的,因此您可以更确定您是否正确保护它)。
  • clean up globals. 清理全局变量。 Globals are bad anyways, and good luck trying to do anything thread-safe with globals. 不管怎样,Globals都很糟糕,并试图用全局变量做任何线程安全的运气。
  • Beware the static keyword. 请注意static关键字。 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. 大多数使用它的人都会以某种微妙的方式弄错,并且很容易受到低级警告的破坏。

That's an incomplete checklist. 这是一份不完整的清单。 I'll add more if I think of it, but hopefully it's enough to get you started. 如果我想到它,我会添加更多,但希望它足以让你开始。

Two things: 两件事情:

1. Make sure you use no globals. 1.确保不使用全局变量。 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. 如果你当前有全局变量,那么让它们成为每线程状态结构的成员,然后让线程将结构传递给公共函数。

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. 现在您可能会问为什么不将x和y作为参数传递。 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. 在现实生活中,你的状态结构可能有20个字段,并且通过大多数这些参数4-5个函数变得令人生畏。 You'd rather pass one parameter instead of many. 你宁愿传递一个参数而不是许多参数。

2. If your threads have data in common that needs to be shared, then you need to look into critical sections and semaphores. 2.如果您的线程有共同的数据需要共享,那么您需要查看关键部分和信号量。 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. 每当你的一个线程访问数据时,它就需要阻塞其他线程,然后在访问共享数据时取消阻塞它们。

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 atomic and and atomic_flag keep the thread in a spin count. 原子atomic_flag使线程保持旋转计数。 Mutex just sleeps the thread. Mutex只是睡觉了。 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. 最后一个“ 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. 使用raii成语。 The constructor to lock the critical section and the destructor to unlock it. 用于锁定关键部分的构造函数和用于解锁它的析构函数。

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!! 谢谢!!

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库和静态变量等线程不安全的东西,那么你应该没问题。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM