提升线程 - 安全/保证处理线程中断的方法

时间:2016-01-18 16:28:39

标签: c++ multithreading boost thread-safety boost-thread

我正在使用pthreads将C程序转移到C ++,并且为了使程序多平台,可移植等,必须大量使用Boost库。

过去使用线程时,我的代码通常会有以下形式:

void threadFunc(void* pUserData)
{
    if ( !pUserData )
    {
        return;
    }

    myStruct* pData = (myStruct*)pUserData;
    bool bRun;

    lock(pUserData);
    bRun = pUserData->bRun;
    unlock(pUserData);

    while ( bRun )
    {
        // Do some stuff
        // ...
        // ...
        lock(pUserData);
        bRun = pUserData->bRun;
        unlock(pUserData);
    }

    /* Done execution of thread; External thread must have set pUserData->bRun
     * to FALSE.
     */
}

这正如我所料。当我希望线程关闭时,我只需锁定它访问struct的互斥锁(通常是struct的成员),切换bRun标志,然后线程上join()。对于提升线程,我已经注意到我的代码在执行非阻塞操作时timed_join()超时similar to this SO question的情况。这让我怀疑我没有正确使用增强线程。

问题......

首先,两个通用线程结构中的哪一个对于让线程正确捕获thread_interrupted异常是正确的?

案例A

void threadFunc( void* pUserData )
{
    while ( 1 )
    {
        try
        {
            // Do some stuff
            // ...
            // ...
            boost::this_thread::sleep(boost::posix_time::milliseconds(1));
        }
        catch(boost::thread_interrupted const& )
        {
            // Thread interrupted; Clean up
        }
    }
}

案例B

void threadFunc( void* pUserData )
{
    try
    {
        while ( 1 )
        {
            // Do some stuff
            // ...
            // ...
            boost::this_thread::sleep(boost::posix_time::milliseconds(1));
        }
    }
    catch(boost::thread_interrupted const& )
    {
        // Thread interrupted; Clean up
    }
}

第二,如果我希望线程有机会来捕获中断调用,那么调用代替睡眠的适当的boost函数是什么,而不是sleep()或者放弃当前持有的CPU时间片的剩余部分? From another SO question on this topic,似乎boost::this_thread::interruption_point()电话可能是我正在寻找的,但我不能100%确定它是否会一直有效,从我在SO中读到的内容问题我从中引用了它。

最后,我的理解是,在我的循环中不调用任何方式的boost sleep()函数或某些类似的中断点函数将意味着{{1}将永远超时,我要么:

  • 强行杀死线程。
  • 使用类似我原始C代码的互斥锁保护标志。

这个假设是否正确?

谢谢。

参考

  1. Boost Thread - How to acknowledge interrupttimed_join(),访问2016-01-18。
  2. boost::this_thread::interruption_point() doesn't throw boost::thread_interrupted& exception<https://stackoverflow.com/questions/7316633/boost-thread-how-to-acknowledge-interrupt>,访问2016-01-18。

1 个答案:

答案 0 :(得分:3)

您的代码不是例外安全的。因此,当您等待加入时,您很容易死锁。

如果在持有互斥锁时收到异常,则代码永远不会解锁互斥锁,可能会导致等待线程死锁。

要检查的事项:

  1. 首先,您的代码不会在任何地方显示您如何创建尝试中断的线程。

    需要boost::thread - 受管线程实例上运行它。 这应该是显而易见的,因为没有这样的对象就很难调用boost::thread::interrupt()。尽管如此,我还是要仔细检查这是有序的,因为线程函数的函数签名强烈建议使用本机POSIX pthread_create

      

    一个好主意是检查interrupt_request()will be false for native threads)的返回值:

         
        

    当从使用本机接口创建的线程调用时,boost::this_thread中断相关函数在降级模式下运行,即boost::this_thread::interruption_enabled()返回false。因此,使用boost::this_thread::disable_interruptionboost::this_thread::restore_interruption将不会执行任何操作,只会忽略对boost::this_thread::interruption_point()的调用。

      
  2. 如何实施锁定/解锁功能?同样在这里也是如此。如果您与POSIX / non-boost API混合使用,那么您可能会错过信号。

    在您使用时,请开始使用lock_guardunique_lock,以便您的代码安全。

  3. 关于您的Case A vs Case B问题,案例B最简单。

    案例A未显示退出while循环的计划。你当然可以使用breakreturn(甚至是goto),但是当你展示它时,它将是一个无限循环。

  4. 除了sleep()之外,还有yield()明确放弃了剩余的时间片,同时又是一个中断点。

    我知道你已经说过你不想放弃时间片,但是我不确定你是否意识到这会导致线程烧掉CPU,如果没有工作要做:

    让我觉得非常矛盾,你希望不要放弃剩余的时间片(表示低延迟,高带宽应用程序和无锁上下文)而你正在谈论的同时使用mutex 。显然,后者比仅仅yield部分时间片贵得多,因为它是无锁并发的相反

  5. 因此,让我总结两个好的风格演示,一个锁定和一个无锁(或锁定保守,取决于你的线程同步逻辑的其余部分)。

    锁定演示

    这使用“旧”线程控件,因为它在锁定时很好,IMO:

    <强> Live On Coliru

    #include <boost/thread.hpp>
    
    void trace(std::string const& msg);
    
    struct myStruct {
        bool keepRunning = true;
    
        using mutex_t = boost::mutex;
        using lock_t  = boost::unique_lock<mutex_t>;
    
        lock_t lock() const { return lock_t(mx); }
    
      private:
        mutex_t mutable mx;
    };
    
    void threadFunc(myStruct &userData) {
        trace("threadFunc enter");
    
        do 
        {
            {
                auto lock = userData.lock();
                if (!userData.keepRunning)
                    break;
            } // destructor of unique_lock unlocks, exception safe
    
    
            // Do some stuff
            // ...
            // ...
            trace("(work)");
            boost::this_thread::sleep_for(boost::chrono::milliseconds(100));
        } while (true);
    
        trace("threadFunc exit");
    }
    
    int main() {
        trace("Starting main");
    
        boost::thread_group threads;
        constexpr int N = 4;
        std::vector<myStruct> data(N);
    
        for (int i=0; i < N; ++i)
            threads.create_thread(boost::bind(threadFunc, boost::ref(data[i])));
    
        boost::this_thread::sleep_for(boost::chrono::seconds(1));
    
        trace("Main signaling shutdown");
    
        for (auto& d : data) {
            auto lock = d.lock();
            d.keepRunning = false;
        }
    
        threads.join_all();
        trace("Bye");
    }
    
    void trace(std::string const& msg) {
        static boost::mutex mx;
        boost::lock_guard<boost::mutex> lk(mx);
    
        static int thread_id_gen = 0;
        thread_local int thread_id = thread_id_gen++;
    
        std::cout << "Thread #" << thread_id << ": " << msg << "\n";
    }
    

    输出,例如

    Thread #0: Starting main
    Thread #1: threadFunc enter
    Thread #1: (work)
    Thread #2: threadFunc enter
    Thread #2: (work)
    Thread #3: threadFunc enter
    Thread #3: (work)
    Thread #4: threadFunc enter
    Thread #4: (work)
    Thread #3: (work)
    Thread #1: (work)
    Thread #2: (work)
    Thread #4: (work)
    Thread #1: (work)
    Thread #3: (work)
    Thread #4: (work)
    Thread #2: (work)
    Thread #4: (work)
    Thread #1: (work)
    Thread #3: (work)
    Thread #2: (work)
    Thread #3: (work)
    Thread #4: (work)
    Thread #2: (work)
    Thread #1: (work)
    Thread #2: (work)
    Thread #1: (work)
    Thread #4: (work)
    Thread #3: (work)
    Thread #2: (work)
    Thread #4: (work)
    Thread #1: (work)
    Thread #3: (work)
    Thread #4: (work)
    Thread #2: (work)
    Thread #1: (work)
    Thread #3: (work)
    Thread #4: (work)
    Thread #2: (work)
    Thread #1: (work)
    Thread #3: (work)
    Thread #4: (work)
    Thread #2: (work)
    Thread #1: (work)
    Thread #3: (work)
    Thread #0: Main signaling shutdown
    Thread #4: threadFunc exit
    Thread #2: threadFunc exit
    Thread #1: threadFunc exit
    Thread #3: threadFunc exit
    Thread #0: Bye
    

    Lockfree Demo

    1. 上一个锁定示例的字面翻译:

      <强> Live On Coliru

    2. 但是,现在拥有一个共享的关闭标志可能更有意义:

      <强> Live On Coliru

      #include <boost/thread.hpp>
      
      void trace(std::string const& msg);
      
      struct myStruct {
          int value;
      };
      
      void threadFunc(myStruct userData, boost::atomic_bool& keepRunning) {
          std::string valuestr = std::to_string(userData.value);
          trace("threadFunc enter(" + valuestr + ")");
      
          do 
          {
              if (!keepRunning)
                  break;
      
              // Do some stuff
              // ...
              // ...
              trace("(work" + valuestr + ")");
              boost::this_thread::sleep_for(boost::chrono::milliseconds(100));
          } while (true);
      
          trace("threadFunc " + valuestr + " exit");
      }
      
      int main() {
          boost::atomic_bool running { true };
          trace("Starting main");
      
          boost::thread_group threads;
          constexpr int N = 4;
      
          for (int i=0; i < N; ++i) {
              threads.create_thread(
                  boost::bind(threadFunc, myStruct{i}, boost::ref(running)
              ));
          }
      
          boost::this_thread::sleep_for(boost::chrono::seconds(1));
      
          trace("Main signaling shutdown");
      
          running = false;
      
          threads.join_all();
          trace("Bye");
      }
      
      void trace(std::string const& msg) {
          static boost::mutex mx;
          boost::lock_guard<boost::mutex> lk(mx);
      
          static int thread_id_gen = 0;
          thread_local int thread_id = thread_id_gen++;
      
          std::cout << "Thread #" << thread_id << ": " << msg << "\n";
      }
      

      请注意,使用threadFunc¹

    3. 将数据传递到boost::bind的方式更加“自然C ++”
    4. 使用interruption_point()

      请注意interrupt()州的文档:

        

      效果:如果*this引用执行的线程,请求   thread下次进入其中一个时将被中断   预定义的中断点启用中断,或者如果是   当前在调用其中一个预定义的中断点时被阻止   启用中断 [强调我的]

      值得注意的是,可以暂时禁用中断。 Boost仅为此提供启用RAII的类,因此默认设置为异常安全。如果您的代码使用class disable_interruption or class restore_interruption,则您不必担心这一点。

      <强> Live On Coliru

      #include <boost/thread.hpp>
      
      void trace(std::string const& msg);
      
      struct myStruct {
          int value;
      };
      
      void threadFunc(myStruct userData) {
          std::string valuestr = std::to_string(userData.value);
          trace("threadFunc enter(" + valuestr + ")");
      
          // using `Case B` form from your question
          try {
              do 
              {
                  boost::this_thread::sleep_for(boost::chrono::milliseconds(100)); // Avoids huge output for demo
      
                  trace("(work" + valuestr + ")");
                  boost::this_thread::interruption_point();
              } while (true);
          } catch(boost::thread_interrupted const& tie) {
              trace("threadFunc " + valuestr + " interrupted");
          }
      
          trace("threadFunc " + valuestr + " exit");
      }
      
      int main() {
          trace("Starting main");
      
          boost::thread_group threads;
          constexpr int N = 4;
      
          for (int i=0; i < N; ++i) {
              threads.create_thread(boost::bind(threadFunc, myStruct{i}));
          }
      
          boost::this_thread::sleep_for(boost::chrono::seconds(1));
      
          trace("Main signaling shutdown");
          threads.interrupt_all();
      
          threads.join_all();
          trace("Bye");
      }
      
      void trace(std::string const& msg) {
          static boost::mutex mx;
          boost::lock_guard<boost::mutex> lk(mx);
      
          static int thread_id_gen = 0;
          thread_local int thread_id = thread_id_gen++;
      
          std::cout << "Thread #" << thread_id << ": " << msg << "\n";
      }
      
    5. ¹或lambdas如果你喜欢