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

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

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


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

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

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

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

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

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

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

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

  • 识别跨线程共享的任何数据(如果您错过了它,则无法保护它)
  • 创建一个成员boost::mutex m_mutex并在您尝试访问共享成员数据时使用它(理想情况下,共享数据对于该类是私有的,因此您可以更确定您是否正确保护它)。
  • 清理全局变量。 无论如何,Globals是糟糕的,并且尝试使用全局变量来执行线程安全的任何事情。
  • 注意static关键字。 它实际上不是线程安全的。 所以如果你正在尝试做一个单身人士,它将无法正常工作。
  • 谨防双检锁定范例。 大多数使用它的人会以某种微妙的方式错误地认为它是错误的,并且很容易被低级警告破坏。
  • 这是一个不完整的清单。 如果我想到的话,我会补充更多,但希望这足以让你开始。


    两件事情:

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

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

    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.如果你的线程有共同的数据需要共享,那么你需要查看关键部分和信号量。 每次有一个线程访问数据时,它需要阻止其他线程,然后在完成访问共享数据时取消阻止它们。


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

    不同类型的锁:

    使用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;
    };
    

    使用原子:

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

    atomicand atomic_flag使线程保持旋转计数。 互斥体只是睡眠线程。 如果等待时间太长,可能会更好地睡眠线程。 最后一个“ CRITICAL_SECTION ”保持线程在旋转计数直到消耗时间,然后线程进入睡眠状态。

    如何使用这些关键部分?

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

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

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

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

    我希望你觉得这有帮助。

    谢谢!!

    链接地址: http://www.djcxy.com/p/76135.html

    上一篇: how to make an application thread safe?

    下一篇: How to declare a vector of atomic in C++