zoukankan      html  css  js  c++  java
  • C++11多线程教学(一)

    转载自:http://www.cnblogs.com/lidabo/p/3908705.html

    本篇教学代码可在GitHub获得:https://github.com/sol-prog/threads。

    在之前的教学中,我展示了一些最新进的C++11语言内容:

    • 1. 正则表达式(http://solarianprogrammer.com/2011/10/12/cpp-11-regex-tutorial/)
    • 2. raw string(http://solarianprogrammer.com/2011/10/16/cpp-11-raw-strings-literals-tutorial/)
    • 3. lambda(http://solarianprogrammer.com/2011/11/01/cpp-11-lambda-tutorial/)

    也许支持多线程是C++语言最大的变化之一。此前,C++只能利用操作系统的功能(Unix族系统使用pthreads库),或是例如OpenMP和MPI这些代码库,来实现多核计算的目标。

    本教程意图让你在使用C++11线程上起个头,而不是只把语言标准在这里繁复地罗列出来。

    创建和启动一条C++线程就像在C++源码中添加线程头文件那么简便。我们来看看如何创建一个简单的带线程的HelloWorld:

    #include《iostream》

    #include《thread》

    //This function will be called from a thread

    //该函数将在一条线程中得到调用

    void call_from_thread() {

    std::cout << "Hello, World" << std::endl;

    }

    int main() {

    //Launch a thread

    //启动一条线程

    std::thread t1(call_from_thread);

    //Join the thread with the main thread

    //和主线程协同

    t1.join();

    return 0;

    }

    在Linux系统中,上列代码可采用g++编译:

    g++ -std=c++0x -pthread file_name.cpp

    在安装有Xcode4.x的麦金系统上,可用clang++编译上述代码:

    clang++ -std=c++0x -stdlib=libc++ file_name.cpp

    视窗系统上,可以利用付费代码库,just::thread,来编译多线程代码。但是很不走运,他们没有提供代码库的试用版,我做不了测试。

    在真实世界的应用程序中,函数“call_from_thread”相对主函数而言,独立进行一些运算工作。在上述代码中,主函数创建一条线程,并 在t1.join()处等待t1线程运行结束。如果你在编码中忘记考虑等待一条线程结束运行,主线程有可能抢先结束它自己的运行状态,整个程序在退出的时 候,将杀死先前创建的线程,不管函数“call_from_thread”有没有执行完。

    上面的代码比使用POSIX线程的等价代码,相对来说简洁一些。请看使用POSIX线程的等价代码:

    //This function will be called from a thread

    void *call_from_thread(void *) {

    std::cout << "Launched by thread" << std::endl;

    return NULL;

    }

    int main() {

    pthread_t t;

    //Launch a thread

    pthread_create(&t, NULL, call_from_thread, NULL);

    //Join the thread with the main thread

    pthread_join(t, NULL);

    return 0;

    }

    我们通常希望一次启动多个线程,来并行工作。为此,我们可以创建线程组,而不是在先前的举例中那样创建一条线程。下面的例子中,主函数创建十条为一组的线程,并且等待这些线程完成他们的任务(在github代码库中也包含这个例子的POSIX版本):

    ...

    static const int num_threads = 10;

    ...

    int main() {

    std::thread t[num_threads];

    //Launch a group of threads 启动一组线程

    for (int i = 0; i < num_threads; ++i) {

    t[i] = std::thread(call_from_thread);

    }

    std::cout << "Launched from the mainn";

    //Join the threads with the main thread

    for (int i = 0; i < num_threads; ++i) {

    t[i].join();

    }

    return 0;

    }

    记住,主函数也是一条线程,通常叫做主线程,所以上面的代码实际上有11条线程在运行。在启动这些线程组之后,线程组和主函数进行协同(join)之前,允许我们在主线程中做些其他的事情,在教程的结尾部分,我们将会用一个图像处理的例子来说明之。

    在线程中使用带有形参的函数,是怎么一回事呢?C++11允许我们在线程的调用中,附带上所需的任意参数。为了举例说明,我们可以修改上面的代码,以接受一个整型参数(在github代码库中也包含这个例子的POSIX版本):

    static const int num_threads = 10;

    //This function will be called from a thread

    void call_from_thread(int tid) {

    std::cout << "Launched by thread " << tid << std::endl;

    }

    int main() {

    std::thread t[num_threads];

    //Launch a group of threads

    for (int i = 0; i < num_threads; ++i) {

    t[i] = std::thread(call_from_thread, i);

    }

    std::cout << "Launched from the mainn";

    //Join the threads with the main thread

    for (int i = 0; i < num_threads; ++i) {

    t[i].join();

    }

    return 0;

    }

    在我的系统上,上面代码的执行结果是:

    Sol$ ./a.out

    Launched by thread 0

    Launched by thread 1

    Launched by thread 2

    Launched from the main

    Launched by thread 3

    Launched by thread 5

    Launched by thread 6

    Launched by thread 7

    Launched by thread Launched by thread 4

    8L

    aunched by thread 9

    Sol$

    能看到上面的结果中,程序一旦创建一条线程,其运行存在先后秩序不确定的现象。程序员的任务就是要确保这组线程在访问公共数据时不要出现阻塞。最后 几行,所显示的错乱输出,表明8号线程启动的时候,4号线程还没有完成在stdout上的写操作。事实上假定在你自己的机器上运行上面的代码,将会获得全 然不同的结果,甚至是会输出些混乱的字符。原因在于,程序内的11条线程都在竞争性地使用stdout这个公共资源 (案:Race Conditions)。

    要避免上面的问题,可以在代码中使用拦截器(barriers),如std:mutex,以同步(synchronize)的方式来使得一群线程访 问公共资源,或者,如果可行的话,为线程们预留下私用的数据结构,避免使用公共资源。我们在以后的教学中,还会讲到线程同步问题,包括使用原子操作类型 (atomic types)和互斥体(mutex)。

    从原理上讲,编写更加复杂的并行代码所需的概念,我们已经在上面的代码中都谈到了。

    接下来的例子,我要来展示并行编程方案的强大之处。这是个稍为复杂的问题:利用柔化滤波器(blur filter)去除一张图片的杂点。思路是利用一点像素和它相邻像素的加权均值的某种算法形式(案:后置滤波),去除图片杂点。

    本教程不在于讨论优化图像处理,笔者也非此路专家,所以我们只采取相当简单的方法。我们的目标是勾勒出如何去编写并行代码,至于如何高效访问图片, 与滤波器的卷积计算,都不是重点。我在此作为举例,只利用空间卷积的定义,而不是采用更多的共振峰(?),当然稍微有些实现上的难度,频域的卷积使用快速 傅里叶变换。

    为简便起见,我们将使用一种简单的非压缩图像文件PPM。接下来,我们提供一个简化的C++类的头文件,这个类负责读写PPM图片,并在内存中的三个无符号字符型数组结构里(RGB三色)重建图像:

    class ppm {

    bool flag_alloc;

    void init();

    //info about the PPM file (height and width)

    //PPM文件的信息(高和宽)

    unsigned int nr_lines;

    unsigned int nr_columns;

    public:

    //arrays for storing the R,G,B values

    //保存RGB值的数组

    unsigned char *r;

    unsigned char *g;

    unsigned char *b;

    //

    unsigned int height;

    unsigned int width;

    unsigned int max_col_val;

    //total number of elements (pixels)

    //元素(像素)的总量

    unsigned int size;

    ppm();

    //create a PPM object and fill it with data stored in fname

    //创建一个PPM对象,装载保存在文件fname中的数据

    ppm(const std::string &fname);

    //create an "empty" PPM image with a given width and height;the R,G,B arrays are filled  //with zeros

    //创建一个“空”PPM图像,大小由_width和_height指定;RGB数组置为零值

    ppm(const unsigned int _width, const unsigned int _height);

    //free the memory used by the R,G,B vectors when the object is destroyed

    //在本对象销毁时,释放RGB向量占用的内存

    ~ppm();

    //read the PPM image from fname

    //从fname文件中读取PPM图像

    void read(const std::string &fname);

    //write the PPM image in fname

    //保存PPM图像到fname文件

    void write(const std::string &fname);

    };

    一种可行的编码方案是:

    • 载入图像到内存。
    • 把图像拆分为几个部分,每部分由相应线程负责,线程数量为系统可承受之最大值,例如四核心计算机可启用8条线程。
    • 启动若干线程——每条线程负责处理它自己的图像块。
    • 主线程处理最后的图像块。
    • 与主线程协调并等待全部线程计算完成。
    • 保存处理后的图像。

    接下来我们列出主函数,该函数实现了如上算法(感谢wiched提出的代码修改意见):

    int main() {

    std::string fname = std::string("your_file_name.ppm");

    ppm image(fname);

    ppm image2(image.width, image.height);

    //Number of threads to use (the image will be divided between threads)

    //采用的线程数量(图像将被分割给每一条线程去处理)

    int parts = 8;

    std::vectorbnd = bounds(parts, image.size);

    std::thread *tt = new std::thread[parts - 1];

    time_t start, end;

    time(&start);

    //Lauch parts-1 threads

    //启动parts-1个线程

    for (int i = 0; i < parts - 1; ++i) {

    tt[i] = std::thread(tst, &image, &image2, bnd[i], bnd[i + 1]);

    }

    //Use the main thread to do part of the work !!!

    //使用主线程来做一部分任务!

    for (int i = parts - 1; i < parts; ++i) {

    tst(&image, &image2, bnd[i], bnd[i + 1]);

    }

    //Join parts-1 threads 协同parts-1条线程

    for (int i = 0; i < parts - 1; ++i)

    tt[i].join();

    time(&end);

    std::cout << difftime(end, start) << " seconds" << std::endl;

    //Save the result 保存结果

    image2.write("test.ppm");

    //Clear memory and exit 释放占用的内存,然后退出

    delete [] tt;

    return 0;

    }

    请无视图像文件名和线程启动数的硬性编码。在实际应用中,应该让用户可以交互式输入这些参数。

    现在为了看看并行代码的工作情况,我们需要赋之以足够任务负荷,否则那些创建和销毁线程的开销将会干扰测试结果,使得我们的并行测试失去意义。输入 的图像应该足够大,以能显示出并行代码性能方面的改进效果。为此,我采用了一张16000x10626像素大小的PPM 格式图片,空间占用约 512MB:

    我用Gimp软件往图片里掺入了一些杂点。杂点效果如下图:

    前面代码的运行结果:

    正如所见,上面的图片杂点程度被弱化了。

    样例代码运行在双核MacBook Pro上的结果:

    Compiler Optimization Threads Time Speed up
    clang++ none 1 40s  
    clang++ none 4 20s 2x
    clang++ -O4 1 12s  
    clang++ -O4 4 6s 2x

    在双核机器上,并行比串行模式(单线程),速率有完美的2倍提升。

    我还在一台四核英特尔i7Linux机器上作了测试,结果如下:

    Compiler Optimization Threads Time Speed up
    g++ none 1 33s  
    g++ none 8 13s 2.54x
    g++ -O4 1 9s  
    g++ -O4 8 3s 3x

    显然,苹果的clang++在提升并行程序方面要更好些,不管怎么说,这是编译器/机器特性的一个联袂结果,也不排除MacBook Pro使用了8GB内存的因素,而Linux机器只有6GB。

    如果有兴趣学习新的C++11语法,我建议阅读《Professional C++》,或《C ++ Primer  Plus》。C++11多线程主题方面,建议阅读《C++ Concurrency in Action》,这是一本好书。

    from:http://article.yeeyan.org/view/234235/268247

  • 相关阅读:
    poj 3321 Apple Tree
    hdu 1520 Anniversary party
    Light OJ 1089 Points in Segments (II)
    Timus 1018 Binary Apple Tree
    zoj 3299 Fall the Brick
    HFUT 1287 法默尔的农场
    Codeforces 159C String Manipulation 1.0
    GraphQL + React Apollo + React Hook 大型项目实战(32 个视频)
    使用 TypeScript & mocha & chai 写测试代码实战(17 个视频)
    GraphQL + React Apollo + React Hook + Express + Mongodb 大型前后端分离项目实战之后端(19 个视频)
  • 原文地址:https://www.cnblogs.com/n-u-l-l/p/5396923.html
Copyright © 2011-2022 走看看