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》也可以。

时间: 2024-11-16 10:42:21

C++11多线程教学(二)的相关文章

C++11多线程教学(一)

本篇教学代码可在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/)

【阿里聚安全·安全周刊】阿里双11技术十二讲直播预约|AWS S3配置错误曝光NSA陆军机密文件

关键词:阿里双11技术十二讲直播丨雪人计划丨亚马逊AWS S3配置错误丨2018威胁预测丨MacOS漏洞丨智能风控平台MTEE3丨黑客窃取<权利的游戏>剧本|Android 8.1 本周资讯top3 [技术直播]承担双11万亿流量,阿里核心技术揭秘:12位大咖告诉你! 分享主题有:<阿里下一代技术架构:云化架构演进之路>.<2017双11供应链的那些事儿>.<分布式存储系统盘古在双11中的战役>.<双11中的智能化网络实践>······ http

【预告:直播回顾&amp;资料下载】2017阿里巴巴双11技术十二讲,历数双11精彩技术干货

峰会专题:[2017阿里巴巴双11技术十二讲]https://yq.aliyun.com/promotion/428感谢各位参与此次直播活动,目前相关活动视频.整理文章已经在紧锣密鼓的编辑中,后续会第一时间在此文中发布,敬请期待! 12月13日议题 阿里下一代技术架构:云化架构演进之路讲师:叔同 / 阿里巴巴资深技术专家 演讲视频:即将发布演讲整理文章:即将发布PDF下载:即将发布 2017双11供应链的那些事儿讲师粤谦 / 阿里巴巴资深技术专家 演讲视频:即将发布演讲整理文章:即将发布PDF下

C++11 多线程

C++11开始支持多线程编程,之前多线程编程都需要系统的支持,在不同的系统下创建线程需要不同的API如pthread_create(),Createthread(),beginthread()等,使用起来都比较复杂,C++11提供了新头文件<thread>.<mutex>.<atomic>.<future>等用于支持多线程. 使用C++11开启一个线程是比较简单的,下面来看一个简单的例子: #include <thread> #include &

从零开始学.net多线程系列(二)

线程的生命周期 下面的图片展示了大部分线程的通常状态,以及当一个线程从某种状态切换成另一种状态时发生的某些动作: 这里有一个关于线程状态的列表: 状态 描述 Running 线程已经被启动,并且没有被阻塞,没有挂起的ThreadAbortException. StopRequested 线程正在请求停止,该状态只供内部使用. SuspendRequested 现在正在请求暂停. Background 线程将被作为后台线程执行,而不是前台线程.该状态是通过设置Thread.IsBackground

quartus verilog-quartus conmpile到11%一直持续二十分钟

问题描述 quartus conmpile到11%一直持续二十分钟 原来的代码compile一次需要不到十分钟,我只是加了几行代码就二十分钟还是停在11%

《Spring 5官方文档》11集成测试 (二)

11.3 JDBC测试支持 org.springframework.test.jdbc是包含JdbcTestUtils的包,它是一个JDBC相关的工具方法集,意在简化标准数据库测试场景.特别地,JdbcTestUtils提供以下静态工具方法: countRowsInTable(..):统计给定表的行数. countRowsInTableWhere(..):使用提供的where语句进行筛选统计给定表的行数. deleteFromTables(..):删除特定表的全部数据. deleteFromTa

Java 并发/多线程教程(二)-多线程的优点

        本系列译自jakob jenkov的Java并发多线程教程,个人觉得很有收获.由于个人水平有限,不对之处还望矫正!      尽管多线程有诸多的挑战,但是多线程被广泛使用的原因有以下几点: 1.对资源的充分利用. 2.简化程序设计 3.响应的及时性 资源的充分利用        假设一个应用程序从本地文件系统中读取并处理一个文件,让我们来假设从硬盘读取文件需要5秒,处理文件需要两秒,那么处理两个文件则需要: 5秒 读取A文件 2秒 处理A文件 5秒 读取B文件 2秒 处理B文件

多线程售票(二)

package cn.thr; //线程实现的方法二:实现runnable接口.多采用此方法来避免单继承的局限性 //线程的例子:总共有10张票,开两个窗口买票. class TicketDemo2 implements Runnable { int ticket = 20; @Override public void run() { while (ticket >= 1) { ticket--; System.out.println(Thread.currentThread().getName