根本没有正确的选择,我们只有靠奋斗来使当初的选择显得正确。

—— 一个有思考的程序员

【C++11多线程】

标签:
std::thread
std::mutex
std::atomic
std::conditional_varible
我们对比单线程和多线程的时间性能:
我们用 Sleep(5000) 函数让程序休眠 5 秒用来模拟函数执行了一系列耗时的操作
  #include <iostream>
  #include <thread>
  #include <time.h>
  #include <Windows.h>

  void func1()
  {
      std::cout << "\n开始执行 func1()......" << std::endl;
      Sleep(5000);
      std::cout << "func1() 执行完毕!" << std::endl;
  }

  void func2()
  {
      std::cout << "\n开始执行 func2()......" << std::endl;
      Sleep(5000);
      std::cout << "func2() 执行完毕!" << std::endl;
  }
  
接下来我们分别用 单线程 和 多线程 来调用这两个函数,对比执行的时间:
1.单线程串行执行
  int main(void)
  {
      // 开始计时
      time_t _start, _end;
      _start = clock();

      // 单线程串行执行
      func1();
      func2();

      // 结束计时,查看结果
      _end = clock();
      std::cout << "\n用时 " << (_end - _start) / 1000 << "秒" << std::endl << std::endl;

      system("pause");
      return;
  }
  
2.多线程并行执行
  int main(void)
  {
      // 开始计时
      time_t _start, _end;
      _start = clock();

      // 多线程并行执行
      std::thread _subThread1(func1);
      std::thread _subThread2(func2);
      _subThread1.detach();
      _subThread2.join();

      // 结束计时,查看结果
      _end = clock();
      std::cout << "\n用时 " << (_end - _start) / 1000 << "秒" << std::endl << std::endl;

      system("pause");
      return;
  }
  
可以看到用单线程执行 func1() 和 func2() 共耗时 10 秒,是 func1() 和 func2() 单独执行的时间总和,而用多线程执行完2个函数总共耗时 5 秒,因为他们是并行同时执行的,并不是 func1() 执行完才执行 func2() 而是 func1() 和 func2() 同时执行的
上面我们假设 func1() 和 func2() 是毫无关系的两个独立逻辑的函数,如果我们的两个函数要进行共享数据,就不能像单线程一样随意对共有变量进行赋值,因为2个线程同时对一块内存进行写入程序就会崩溃,此时就要用到锁:std::mutex
  #include <iostream>
  #include <thread>
  #include <queue>
  #include <mutex>

  std::queue<unsigned long long> s_shareData;    // 共享队列
  std::mutex s_lock;                             // 全局锁

  // 生产者
  void makeData()
  {
      unsigned long long _value = 0;
      while (true)
      {
          s_lock.lock();                // 加锁
          s_shareData.push(_value);     // 存数据
          s_lock.unlock();              // 解锁
          _value++;
      }
  }

  // 消费者
  void useData()
  {
      while (true)
      {
          s_lock.lock();                                          // 加锁
          if (!s_shareData.empty())
          {
              unsigned long long _value = s_shareData.front();    // 取数据
              s_shareData.pop();
              s_lock.unlock();                                    // 解锁
              std::cout << "data: " << _value << std::endl;
              continue;
          }
          s_lock.unlock();
      }
  }

  // 使用
  int main(void)
  {
      // 生产者线程
      std::thread s1(makeData);
      s1.detach();

      // 消费者线程(主线程)
      useData();

      return 0;
  }
  
假设你正乘坐通宵列车旅行。一个可以确保你在正确的车站下车的方法就是整夜保持清醒并注意火车停靠的地方。你不会误站,但你到那儿时就会觉得很累。或者,你可以查一下时间表,了解火车会在何时到达,将闹钟定的稍微提前一点,然后去睡觉。这是可以的;你不会错过站,但是如果火车晚点了,你就会醒的太早。也有可能闹钟的电池没电了,你就会睡过头以至于错过站。理想的状况是,你只管去睡觉,让某个人或某个东西在火车到站时叫醒你,无论何时。
上面我们就用了最笨的第一种方法,在while(true)里不停的访问共享队列查看是否有数据,如果有就取出,如果没有就继续查看,这种方法的弊端是如果长时间没有数据,消费者就会不停的访问空队列,极大的浪费CPU,下面我们用std::conditional_varble让消费者在没有数据的时候休眠线程,让CPU去处理别的事情,当有数据时主动唤醒线程去取数据
  #include <iostream>
  #include <thread>
  #include <queue>
  #include <mutex>
  #include <condition_variable>

  std::queue<unsigned long long>    s_shareData;    // 共享队列
  std::mutex                        s_lock;         // 全局锁
  std::condition_variable           s_conditional;  // 唤醒条件

  // 生产者
  void makeData()
  {
      unsigned long long _value = 0;
      while (true)
      {
      	  if (_value % 100000000 == 0)       // 隔一亿个数生产一个数据(因为C++太快,在我的机器上不到一秒产生一个数据)
      	  {
      	      s_lock.lock();                 // 加锁
              s_shareData.push(_value);      // 存数据
              s_lock.unlock();               // 解锁
              s_conditional.notify_one();    // 唤醒休眠的线程通知去取数据
      	  }
          _value++;
      }
  }

  // 消费者
  void useData()
  {
      while (true)
      {
          std::unique_lock<std::mutex> _lock(s_lock);                         // 加锁
          s_conditional.wait(_lock, [](){ return !s_shareData.empty(); });    // 如果队列为空(lambda函数返回false)
                                                                              // 线程就休眠到这一行不往下执行,
                                                                              // 直到收到唤醒通知
          unsigned long long _value = s_shareData.front();                    // 取数据
          s_shareData.pop();
          _lock.unlock();                                                     // 解锁
          std::cout << "data: " << _value << std::endl;
      }
  }

  // 使用
  int main(void)
  {
      // 生产者线程
      std::thread s1(makeData);
      s1.detach();

      // 消费者线程(主线程)
      useData();

      return 0;
  }