• C++11多线程教学(二)


    C++11多线程教学II

    从我最近发布的C++11线程教学文章里,我们已经知道C++11线程写法与POSIX的pthreads写法相比,更为简洁。只需很少几个简单概念,我们就能搭建相当复杂的处理图片程序,但是我们回避了线程同步的议题。在接下来的部分,我们将进入C++11多线程编程的同步领域,看看如何来同步一组并行的线程。

    我们快速回顾一下如何利用c++11创建线程组。上次教学当中,我们用传统c数组保存线程,也完全可以用标准库的向量容器,这样做更有c++11的气象,同时又能避免使用new和delete来动态分配内存所带来的隐患。

    #include
    #include
    #include

    //This function will be called from a thread线程将调用此函数

    void func(int tid) {
        std::cout << "Launched by thread " << tid << std::endl;
    }

    int main() {
        std::vectorth;

        int nr_threads = 10;

        //Launch a group of threads 启动一组线程
        for (int i = 0; i < nr_threads; ++i) {
            th.push_back(std::thread(func,i));
        }

        //Join the threads with the main thread 与主线程协同运转
        for(auto &t : th){
            t.join();
        }

        return 0;
    }

    在Mac OSX Lion上用clang++或gcc-4.7编译上述程序:

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

    g++-4.7 -Wall -std=c++11 file_name.cpp

    现代Linux系统上,使用gcc-4.6.x编译代码:

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


    某些活生生的现实问题,其棘手的地方就在于它们天然就是并行方式,上面开头部分写的代码当中,已用很简化的语法实现了这种方式。举一个典型的并行问题:引入两个数组,一个数组与乘数相乘,生成孟得伯特集合。

    线程之间还有同步层次的问题。以向量点乘为例来说,两个等长(维度)向量,他们的元素两两对应相乘,然后乘积相加得到一个标量结果。初略的并行编码方式如下:
     

    #include
    #include
    #include

    ...

    void dot_product(const std::vector&v1, const std::vector&v2, int &result, 
    int L, int R){
        for(int i = L; i < R; ++i){
            result += v1[i] * v2[i];
        }
    }

    int main(){
        int nr_elements = 100000;
        int nr_threads = 2;
        int result = 0;
        std::vectorthreads;

        //Fill two vectors with some constant values for a quick verification 
        // v1={1,1,1,1,...,1}以常量值填充两个向量,便于检验
        // v2={2,2,2,2,...,2}    
        // The result of the dot_product should be 200000 for this particular case
       //当前例子的点乘结果应为200000
        std::vectorv1(nr_elements,1), v2(nr_elements,2);

        //Split nr_elements into nr_threads parts 把nr_elements份计算任务划分为 nr_threads 个部分
        std::vectorlimits = bounds(nr_threads, nr_elements);

        //Launch nr_threads threads: 启动 nr_threads 条线程
        for (int i = 0; i < nr_threads; ++i) {
            threads.push_back(std::thread(dot_product, std::ref(v1), std::ref(v2), 
    std::ref(result), limits[i], limits[i+1]));
        }


        //Join the threads with the main thread 协同 线程组与主线程
        for(auto &t : threads){
            t.join();
        }

        //Print the result打印结果
        std::cout<<result<<std::endl;
     
        return 0;
    }
    上述代码的结果显然应该是200000,但是运行几次出来的结果都有轻微的差异:
     
    sol $g++-4.7 -Wall -std=c++11 cpp11_threads_01.cpp
    sol $./a.out
    138832
    sol $./a.out
    138598
    sol $./a.out
    138032
    sol $./a.out
    140690

    sol $

    怎么回事?仔细看第九行代码,变量result累加v1[i],v2[i]之和。该行是典型的竞争条件,这段代码在两个异步线程中并行运作,变量result可以被任意一方抢先访问而被改变。

    通过规定该变量应同步地由线程来访问,我们可以避免出问题,我们可以采用一个mutex(互斥)来达成目的,mutex是一种特别用途的变量,行为如同一个barrier,同步化访问那段修改result变量的代码:
     
    #include
    #include
    #include
    #include

    static std::mutex barrier;

    ...

    void dot_product(const std::vector&v1, const std::vector&v2, int &result, int L, int R){
        int partial_sum = 0;
        for(int i = L; i < R; ++i){
            partial_sum += v1[i] * v2[i];
        }
        std::lock_guardblock_threads_until_finish_this_job(barrier);
        result += partial_sum;
    }
    ...

    第6行创建一个全局mutex变量barrier,第15行强制线程在完成for循环之后才同步存取result。注意,这一次我们采用了新的变量partial sum,声明为线程局部变量。其他代码部分保持原貌。

    针对这个特定的例子,我们还可以找到更简洁优美的方案,我们可以采用原子类型,这是一种特定的变量类型,能达成安全的同时读写,在底层基本上解决了同步问题。额外注明一下,我们可以使用的原子类型只能用在原子操作上,这些操作都定义在atomic 头文件里面:
     
    #include
    #include
    #include
    #include

    void dot_product(const std::vector&v1, const std::vector&v2, std::atomic&result, int L, int R){
        int partial_sum = 0;
        for(int i = L; i < R; ++i){
            partial_sum += v1[i] * v2[i];
        }
        result += partial_sum;
    }

    int main(){
        int nr_elements = 100000;
        int nr_threads = 2;
        std::atomicresult(0);
        std::vectorthreads;

            ...

        return 0;
    }

    苹果机的clang++当前还不支持原子类型和原子操作,有两个办法可以达到目标,编译最新clang++源码,要么使用最新的gcc-4.7,也需要编译源码。

    想学习c++11新语法,我推荐阅读《Professional C++》第二版,《C++ Primer Plus》也可以。

  • 相关阅读:
    [总结] 二维ST表及其优化
    [51Nod 1515] 明辨是非
    [总结] fhq_Treap 学习笔记
    [ZJOI2008] 骑士
    [SDOI2010] 古代猪文
    [POJ 1006] 生理周期
    [POJ 2891] Strange Way to Express Integers
    [Luogu 1516] 青蛙的约会
    python第十三天
    python第十二天-----RabbitMQ
  • 原文地址:https://www.cnblogs.com/lidabo/p/3908713.html
Copyright © 2020-2023  润新知