• 5分钟速成C++14多线程编程


    原文链接:Learn C++ Multi-Threading in 5 Minutes

      C++14的新的多线程架构非常简单易学,如果你对C或者C++很熟悉,那么本文非常适合你。作者用C++14作为基准参考,但是所介绍的东西在C++17中也依然适用。本文只介绍基本的架构,在读完本文后你应该也可以自己编写自己的多线程程序。

    创建线程

    创建线程有以下几种方式:

    1.使用函数指针

    2.使用仿函数

    3.使用lambda表达式

    这些方式都比较类似,只有部分差别,我将在下面具体讲述每一种方式和他们的区别。

    使用函数指针

      来看看下面这个函数,其参数包括一个vector的引用 v ,一个输出结果的引用acm,还有两个v的索引。这个函数会将v的beginIndex和endIndex之间的元素累加起来。

    1 void accumulator_function2(const std::vector<int> &v, unsigned long long &acm, 
    2                             unsigned int beginIndex, unsigned int endIndex)
    3 {
    4     acm = 0;
    5     for (unsigned int i = beginIndex; i < endIndex; ++i)
    6     {
    7         acm += v[i];
    8     }
    9 }

      现在如果我们想将vector分为两个部分,并在单独的线程t1和t2中分别计算各部分的总和的话,我们可以这么写:

     1 //Pointer to function
     2     {
     3         unsigned long long acm1 = 0;
     4         unsigned long long acm2 = 0;
     5         std::thread t1(accumulator_function2, std::ref(v), 
     6                         std::ref(acm1), 0, v.size() / 2);
     7         std::thread t2(accumulator_function2, std::ref(v), 
     8                         std::ref(acm2), v.size() / 2, v.size());
     9         t1.join();
    10         t2.join();
    11 
    12         std::cout << "acm1: " << acm1 << endl;
    13         std::cout << "acm2: " << acm2 << endl;
    14         std::cout << "acm1 + acm2: " << acm1 + acm2 << endl;
    15     }

    上面这段你需要知道:

      1.std::thread这个调用创建了一个新的线程。其第一个参数是函数指针 accumulator_function2 ,因此每个线程都会去执行这个函数。

      2.剩下的我们传给std::thread的构造函数的参数,都是我们需要去传给accumulator_function2的参数。

      3.重点:传递给accumulator_function2的参数默认情况下都是值传递的,除非你用std::ref把他包起来。所以我们这里使用了std::ref来包住v、acm1、acm2。

      4.使用std::thread创建的线程是没有返回值的,所以如果你想从线程中返回些什么,请使用引用将你想返回的值作为一个传入参数。这里的例子就是acm1和acm2。

      5.每个线程一旦创建就立即执行了。

      6.我们使用join()函数来等待线程执行完毕。

    使用伪函数

      你也可以使用伪函数来做同样的事情,下面是例子:

     1 class CAccumulatorFunctor3
     2 {
     3   public:
     4     void operator()(const std::vector<int> &v, 
     5                     unsigned int beginIndex, unsigned int endIndex)
     6     {
     7         _acm = 0;
     8         for (unsigned int i = beginIndex; i < endIndex; ++i)
     9         {
    10             _acm += v[i];
    11         }
    12     }
    13     unsigned long long _acm;
    14 };

      那么创建线程的方式变成下面这样:

     1 //Creating Thread using Functor
     2     {
     3 
     4         CAccumulatorFunctor3 accumulator1 = CAccumulatorFunctor3();
     5         CAccumulatorFunctor3 accumulator2 = CAccumulatorFunctor3();
     6         std::thread t1(std::ref(accumulator1), 
     7             std::ref(v), 0, v.size() / 2);
     8         std::thread t2(std::ref(accumulator2), 
     9             std::ref(v), v.size() / 2, v.size());
    10         t1.join();
    11         t2.join();
    12 
    13         std::cout << "acm1: " << accumulator1._acm << endl;
    14         std::cout << "acm2: " << accumulator2._acm << endl;
    15         std::cout << "accumulator1._acm + accumulator2._acm : " << 
    16             accumulator1._acm + accumulator2._acm << endl;
    17     }

    上面这段你需要知道:

      伪函数的使用方式大部分地方都和函数指针很像,除了:

      1.第一个参数变成了伪函数对象。

      2.我们不再需要使用引用来获取返回值了,我们可以将返回值作为伪函数对象的一个成员变量来储存。这里的例子就是_acm。

    使用lambda表达式

      作为第三种选择,我们可以在每个线程的构造函数中使用lambda表达式来定义我们想做的事,如下:

     1 {
     2         unsigned long long acm1 = 0;
     3         unsigned long long acm2 = 0;
     4         std::thread t1([&acm1, &v] {
     5             for (unsigned int i = 0; i < v.size() / 2; ++i)
     6             {
     7                 acm1 += v[i];
     8             }
     9         });
    10         std::thread t2([&acm2, &v] {
    11             for (unsigned int i = v.size() / 2; i < v.size(); ++i)
    12             {
    13                 acm2 += v[i];
    14             }
    15         });
    16         t1.join();
    17         t2.join();
    18 
    19         std::cout << "acm1: " << acm1 << endl;
    20         std::cout << "acm2: " << acm2 << endl;
    21         std::cout << "acm1 + acm2: " << acm1 + acm2 << endl;
    22     }

    同样,大多数地方都和函数指针的方式很类似,除了:

      1.作为传参的替代方式,我们可以使用lambda表达式的捕获(capture)方式来处理参数传递

    Tasks, Futures, and Promises

    除了std::thread,我们还可以使用 tasks.

    tasks和std::thread工作的方式非常相似,只有一个最主要的不同:tasks可以返回一个值。因此,你可以暂存这个返回值来作为这个线程的更抽象的定义方式,并在你真的需要返回的结果的时候来从这个返回值中拿到数据。

    下面就是使用Tasks的例子:

     1 #include <future>
     2 //Tasks, Future, and Promises
     3     {
     4         auto f1 = [](std::vector<int> &v, 
     5             unsigned int left, unsigned int right) {
     6             unsigned long long acm = 0;
     7             for (unsigned int i = left; i < right; ++i)
     8             {
     9                 acm += v[i];
    10             }
    11 
    12             return acm;
    13         };
    14 
    15         auto t1 = std::async(f1, std::ref(v), 
    16             0, v.size() / 2);
    17         auto t2 = std::async(f1, std::ref(v), 
    18             v.size() / 2, v.size());
    19 
    20         //You can do other things here!
    21         unsigned long long acm1 = t1.get();
    22         unsigned long long acm2 = t2.get();
    23 
    24         std::cout << "acm1: " << acm1 << endl;
    25         std::cout << "acm2: " << acm2 << endl;
    26         std::cout << "acm1 + acm2: " << acm1 + acm2 << endl;
    27     }

    上面这段你需要知道:

      1.tasks使用std::async创建

      2.std::async的返回值是一个叫std::future的类型。别被他的名字唬到,他的意思是t1和t2的值会在未来被真正的赋值。我们通过调用t1.get()来获得他的真正的返回值。

      3.如果future的返回值还没有准备好(任务还没有计算完成),那么调用get()的主线程会被卡住,直到准备好了返回值(和join()的行为一样)。

      4.注意,我们传递给std::async的函数(实际上是lambda表达式)是有返回值的,这个返回值用过一个叫做std::promise的类型来传递。大多数情况下你不需要了解任何promise的细节,C++在幕后可以处理好这些事情。

      5.默认的情况下,tasks也会在创建之后立刻运行(有办法来修改这个行为,但是本文没有涉及)。

    线程创建的总结:

      创建线程很简单,你可以通过函数指针、伪函数、lambda表达式的方式来创建std::thread,也可以使用std::async的方式来获得一个std::future类型的返回值。std::async也同样可以使用函数指针、伪函数、lambda表达式来创建

    (未完待续)

  • 相关阅读:
    c++ 中的substr
    c++ 中将数字字符串转换成int输出的stoi() 和 atoi()
    c++ 四舍五入函数 round
    stddef.h----常用函数
    local.h-----地区函数
    errno.h-----定义出错代码
    signal.h-----信号函数
    stdio.h----标准的输入输出函数
    time.h-------日期与时间函数
    math.h--------数学函数
  • 原文地址:https://www.cnblogs.com/winnersun/p/10591646.html
Copyright © 2020-2023  润新知