首页 文章

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

提问于
浏览
53

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

谁能请 list out the things to be done or taken care of to make an application thread safe . 如果可能,请提供有关C / C语言的答案 .

4 回答

  • 53

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

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

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

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

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

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

    low-level caveat :编译器可以重新排序语句,这可能会破坏线程安全性 . 对于多个内核,每个内核都有自己的缓存,您需要正确同步缓存以确保线程安全 . 而且,即使编译器今天实际上没有实际可行 . 你可以获得99.99%的方式,并且编译器供应商和cpu制造商正在努力解决这个挥之不去的警告 .

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

    • 识别跨线程共享的任何数据(如果您错过了,则无法保护它)

    • 创建一个成员 boost::mutex m_mutex 并在您尝试访问该共享成员数据时使用它(理想情况下,共享数据对于该类是私有的,因此您可以更确定您是否正确保护它) .

    • 清理全局变量 . 不管怎样,Globals都很糟糕,并试图用全局变量做任何线程安全的运气 .

    • 注意 static 关键字 . 它's actually not thread safe. So if you'试图做一个单身,它将无法正常工作 .

    • 当心双重锁定范例 . 大多数使用它的人都会以某种微妙的方式弄错,并且很容易受到低级警告的破坏 .

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

  • 0

    两件事情:

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

  • 3

    如果要对类的方法进行独占访问,则必须在这些函数中使用锁 .

    不同类型的锁:

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

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

    使用 mutex:

    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 将线程保持在旋转计数中 . Mutex 只是睡觉了 . 如果等待时间太长也许最好睡眠线程 . 最后一个“ CRITICAL_SECTION ”使线程保持旋转计数直到消耗时间,然后线程进入休眠状态 .

    如何使用这些关键部分?

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

    使用raii成语 . 用于锁定关键部分的构造函数和用于解锁它的析构函数 .

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

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

    我希望你觉得这很有帮助 .

    谢谢!!

  • 13

    一个想法是将您的程序视为一系列通过队列进行换向的线程 . 每个线程都有一个队列,这些队列将与所有线程共享(以及共享数据同步方法(如互斥等)) .

    然后"solve" 生产环境 者/消费者问题,但是你想让队列保持下溢或溢出 . http://en.wikipedia.org/wiki/Producer-consumer_problem

    只要你保持你的线程本地化,只是通过队列发送副本来共享数据,而不是访问多线程中的(大多数)gui库和静态变量等线程不安全的东西,那么你应该没问题 .

相关问题