C++11并发编程

C++11开始支持多线程编程,之前多线程编程都需要系统的支持,在不同的系统下创建线程需要不同的API如pthread_create(),Createthread(),beginthread()等。现在C++11中引入了一个新的线程库,C++11提供了新头文件,主要包含 <thread>、<mutex>、<atomic>、<condition_varible>、<future>五个部分;<thread>等用于支持多线程,同时包含了用于启动、管理线程的诸多工具,同时,该库还提供了包括像互斥量、锁、原子量等在内的同步机制。在这个系列的文章中,我将尝试和大家分享这个新库提供的部分特性

为了让你的代码支持多线程库,你环境上的编译器必须支持 C++11,不过幸运的是现在绝大部分的编译器都支持C++11了,除非你手头上的编译器版本是古董级别了,真这样的话,还是建议你赶快升级,不光是为了这次的学习,后面你肯定会用上的。

多线程编程的好处

在看本文之前,如果对什么是线程、什么是进程仍存有疑惑,请先Google之,这两个概念不在本文的范围之内。用多线程只有一个目的,那就是更好的利用cpu的资源,下面两个图很好的反应了这点。

以单cpu为例,假如有个进程,进程功能是从网络上接受数据,将接收到的数据进行处理。我们知道,网络上的数据是有时延的,甚至在你接收数据的这段时间内由于网络等问题接收不到数据,如果是单线程的进程,那么此时你的进程什么也做不了,苦苦的在等待对端的数据。此时cpu被浪费了(假设系统就这一个进程,不考虑将进程切换到其它进程的)。

如果进程含有两个线程,一个线程专门接收数据,暂且叫做A线程吧;另外一个线程专门负责处理接收的数据,就叫B线程吧。那么情况就不一样了,当由于网络等问题接收不到数据时,阻塞的只是A线程,完全可以通过某种调度机制将cpu调度给B线程让其工作。这样,cpu得到了最大的利用而不至于浪费在那儿。

好了,既然线程的用处这么大,C++11又加了支持多线程的库,那么让我们一窥其庐山真面目吧。

如何启动一个线程

在C++11中,启动一个新的线程非常简单,当你创建一个 std::thread 的实例时,它便会自行启动。同时在创建线程实例时,须提供给该线程一段将要执行的函数,方法之一是在创建线程实例传递一个函数指针。

好吧,不管学什么编程语言,Hello world!感觉总是不会少的,这仿佛已经成为了一个标准。这次我们仍以经典的 "Hello world” 为例来向大家如何创建一个线程:

#include <thread>
#include <iostream>

void hello()
{
    std::cout << "Hello world from thread !"               << std::endl;
}

int main()
{
    std::thread t(hello);
    t.join();

    return 0;
}
[[email protected] c++]# g++ -std=c++11 -o a a.cpp
[[email protected] c++]# ./a
terminate called after throwing an instance of 'std::system_error'
  what():  Enable multithreading to use std::thread: Operation not permitted
已放弃

云行时报错了,编译的时候加上-lpthread

[[email protected] c++]# g++ -std=c++11 -lpthread -o a a.cpp
[[email protected] c++]# ./a
Hello world from thread !

看到了吧,首先,我们引入了头文件<thread>。在这个头文件中,C++11 提供了管理线程的类和函数。之后,我们定义了一个无返回类型的函数hello,这个函数除了在标准输出流中打印一行文字之外什么也不做。而后,我们定义了一个 std::thread 类型的变量 t,在定义的时候用hello函数名(其实就是一个函数指针)作为 std::thread 类构造函数的参数 。

这个例子中值得注意的是函数 join()。调用join()将导致当前线程等待被 join 的线程结束(在本例中即线程 main 必须等待线程 t结束后方可继续执行)。如果你不调用 join() ,其结果是未定义的 —— 程序可能如你所愿打印出 "Hello world  from thread" 以及一个换行,但是也可能只打印出 "Hello world  from thread" 却没有换行,甚至什么都不做,那是因为线程 main 可能在线程 t结束之前就返回了。

其次还需注意的就是在编译的时候需加上参数-std=c++11,否则编译不过。

使用Lambda表达式启动线程

如果线程所要执行的代码非常短小时,你完全没必要专门为之创建一个函数,取而代之的是使用 Lambda 表达式(关于Lambda 表达式,有兴趣的朋友可以上网查查它的用法,它也是C++11新加入的特性)。我们可以很轻易地将上述例子改写为使用 Lambda 表达式的形式:

#include <thread>
#include <iostream>

int main()
{
    std::thread t([](){
       std::cout << "Hello world from thread                !"<< std::endl;});
       t.join();

    return 0;
}

[[email protected] c++]# ./b

Hello world from thread                !

如上,我们使用了 Lambda 表达式替换掉原来的函数指针。不需要任何怀疑,这段代码和之前使用函数指针的代码实现了完全相同的功能。

给你的线程执行函数加上参数

是不是觉得开启一个线程就是为了打印一句话有点小题大做是吧,好的,我们现在是该做点什么了。在上个线程函数的基础上,除了打印一句话,我们再加个功能:接受两个参数,计算它们的和并打印出。

#include <thread>
#include <iostream>

//线程执行函数接受两个int类型参数
void hello(int x,int y)
{
   std::cout<<"Hello world from thread !" <<std::endl;
   std::cout<<"x+y=" <<x+y <<std::endl;
}

int main()
{
  std::thread t (hello,10,20);
  t.join();
  return 0;
}
[[email protected] c++]# ./cHello world from thread !x+y=30

程序的运行结果正是我们想要的,没错,向线程函数传参就是这么简单:在创建线程实例的时候和函数指针一起传递过去

区分线程

我们可以回一下我们是如何区分进程的?每个进程都有一个编号,称为pid(进程id),我们就是通过它来区分不同的进程,不光是我们人,其实操作系统也是通过这个pid来区分管理不同的进程。

线程也一样, 每个线程都有唯一的 ID 以便我们加以区分,我们称之为tid(线程id)。使用 std::this_thread 类的 get_id() 便可获取对应线程的tid。下面的例子将创建一些线程并使用 std::this_thread::get_id() 来获取当前线程的tid,并打印出:

#include <thread>
#include <iostream>
#include <vector>

void hello()
{   
    std::cout << "Hello world from thread: "               << std::this_thread::get_id()               << std::endl;
}

int main()
{
    std::vector<std::thread> threads;
    for(int i = 0; i < 6; ++i)
    { 
        threads.push_back(std::thread(hello));
    }
    for(auto& thread : threads)
    {
        thread.join();
    }

   return 0;
}
[[email protected] c++]# ./thread
Hello world from thread: Hello world from thread: Hello world from thread: Hello world from thread: 140598791485184
140598816663296140598799877888

Hello world from thread: 140598783092480
Hello world from thread: 140598774699776
140598808270592
[[email protected] c++]# ./thread
Hello world from thread: 140198417712896
Hello world from thread: 140198409320192
Hello world from thread: 140198392534784
Hello world from thread: 140198375749376
Hello world from thread: 140198384142080
Hello world from thread: 140198400927488

看到没,上述结果我肯定不是你所希望看到的,结果似乎混乱了,如果你在你的电脑上多运行几遍,甚至还会出现其它各种结果,那么这种匪夷所思的运行结果究竟是什么原因导致的?

我们知道线程之间是交叉运行的,在上面这个例子,我们并没有去控制线程的执行顺序,某个线程在运行期间可能随时被抢占, 同时可以看到,上面例子的ostream 分几个步骤(首先输出一个 string,然后是 ID,最后输出换行),因此三个线程可能先都只执行了第一个步骤将 Hello world from thread 打印出来,然后每个线程都依次执行完剩余的两个步骤(打印ID,然后换行),这就导致了上面的运行结果。

那么有没有方法解决上面的问题了,答案是肯定的,接下来我们将看到如何使用锁机制控制线程的执行顺序。

#include <thread>
#include <iostream>
#include <vector>

pthread_mutex_t  lock = PTHREAD_MUTEX_INITIALIZER;

void hello()
{   
    pthread_mutex_lock(&lock);       
    std::cout << "Hello world from thread: "               << std::this_thread::get_id()               << std::endl;
    pthread_mutex_unlock(&lock);
}

int main()
{
    std::vector<std::thread> threads;
    for(int i = 0; i < 6; ++i)
    { 
        //pthread_mutex_lock(&lock);
        threads.push_back(std::thread(hello));
        //pthread_mutex_unlock(&lock);
    }
    for(auto& thread : threads)
    {
        thread.join();
    }

   return 0;
}
[[email protected] c++]# ./thread
Hello world from thread: 140515972613888
Hello world from thread: 140515939043072
Hello world from thread: 140515930650368
Hello world from thread: 140515964221184
Hello world from thread: 140515955828480
Hello world from thread: 140515947435776
[[email protected] c++]# ./thread
Hello world from thread: 140654524454656
Hello world from thread: 140654532847360
Hello world from thread: 140654516061952
Hello world from thread: 140654507669248
Hello world from thread: 140654499276544
Hello world from thread: 140654490883840

看到没有,现在再执行thread程序的时候,就不会乱了。

转载了码农有道,并把后部分完成了。

原文地址:http://blog.51cto.com/59090939/2055150

时间: 2024-08-04 11:58:04

C++11并发编程的相关文章

C++11 并发编程基础(一):并发、并行与C++多线程

正文 C++11标准在标准库中为多线程提供了组件,这意味着使用C++编写与平台无关的多线程程序成为可能,而C++程序的可移植性也得到了有力的保证.另外,并发编程可提高应用的性能,这对对性能锱铢必较的C++程序员来说是值得关注的. 回到顶部 1. 何为并发 并发指的是两个或多个独立的活动在同一时段内发生.生活中并发的例子并不少,例如在跑步的时候你可能同时在听音乐:在看电脑显示器的同时你的手指在敲击键盘.这时我们称我们大脑并发地处理这些事件,只不过我们大脑的处理是有次重点的:有时候你会更关注你呼吸的

使用 C++11 并发编程入门

一.认识并发和并行 先将两个概念, 并发与并行 并发:同一时间段内可以交替处理多个操作: 图中整个安检系统是一个并发设计的结构.两个安检队列队首的人竞争这一个安检窗口,两个队列可能约定交替着进行安检,也可能是大家同时竞争安检窗口(通信).后一种方式可能引起冲突:因为无法同时进行两个安检操作.在逻辑上看来,这个安检窗口是同时处理这两个队列 并行:同一时刻内同时处理多个操作: 图中整个安检系统是一个并行的系统.在这里,每个队列都有自己的安检窗口,两个队列中间没有竞争关系,队列中的某个排队者只需等待队

C++11并发编程:原子操作atomic

一:概述 项目中经常用遇到多线程操作共享数据问题,常用的处理方式是对共享数据进行加锁,如果多线程操作共享变量也同样采用这种方式. 为什么要对共享变量加锁或使用原子操作?如两个线程操作同一变量过程中,一个线程执行过程中可能被内核临时挂起,这就是线程切换,当内核再次切换到该线程时,之前的数据可能已被修改,不能保证原子操作. C++11提供了个原子的类和方法atomic,保证了多线程对变量原子性操作,相比加锁机制mutex.locak(),mutex.unlocak(),性能有几倍的提升. 所需头文件

[C++11 并发编程] 06 - Mutex 死锁

假设有两个线程,在执行某些操作时,都需要锁定一对mutex,线程A锁定了mutex A,而线程B锁定了额mutex B,它们都在等待对方释放另一个mutex,这就会导致这两个线程都无法继续执行.这种情况就是死锁. 避免死锁最简单的方法是总是以相同的顺序对两个mutex进行锁定,比如总是在锁定mutex B之前锁定mutex A,就永远都不会死锁. 假设有一个操作要交换同一个类的两个实例的内容,为了交换操作不被并发修改影响,我们需要锁定这两个实例内部的mutex.但是,如果选定一个固定的顺序来锁定

[C++11 并发编程] 01 - Hello World

C++11标准支持了并发,其中包含了线程管理,共享资源保护,线程间同步操作和底层原子操作等功能.我们先通过一个简单的示例看看C++11标准的多线程程序是什么样的. #include <iostream> #include <thread> // 引用用于管理线程的类的头文件 using namespace std; // 线程的入口函数,程序将在新创建的线程中打印log void hello() { cout << "Hello Concurrent Worl

C++11并发编程入门

也许有人会觉得多线程和并发难用,复杂,还会让代码出现各种各样的问题,不过,其实它是一个强有力的工具,能让程序充分利用硬件资源,让程序运行得更快. 何谓并发: 两个或更多独立得活动同时发生.计算机中就是单个系统同时执行多个独立的任务,通过这个任务做一会儿,再切换到别的任务再做一会儿的方式,让任务看起来是并行执行的.切换就是做上下文切换,会有时间开销,操作系统为当前运行的任务保存CPU的状态和指针,计算出要切换到哪一个任务,并为即将切换到的任务重新加载处理器状态. 并发的方式: 多进程并发 多线程并

[C++11 并发编程] 06 Mutex race condition

上一节中介绍了mutex的基本使用方法,使用mutex来保护共享数据并不能解决race condition带来的问题,假如我们有一个堆栈数据结构类似于std::stack它提供了5个基本操作push(),pop(),top(),empty(),和size().这里的top()操作返回栈顶元素的拷贝,这样我们就可以使用一个mutex来保护栈内部的数据.但是race codition情况下,虽然使用mutex在stack的每个接口内都对共享数据进行了保护,仍然有问题存在. #include <deq

[C++11 并发编程] 04 动态选择并发线程的数量

C++标准模板库提供了一个辅助函数 - std::thread::hardware_concurrency(),通过这个函数,我们可以获取应用程序可以真正并发执行的线程数量.下面这个例子,实现了一个并发版本的std::accumulate,它将工作拆分到多个线程中,为了避免过多线程带来的开销,程序指定了每个线程处理数据的最小数量. 头文件和求和操作: #include <thread> #include <numeric> #include <algorithm> #i

[C++11 并发编程] 08 - Mutex std::unique_lock

相对于std::lock_guard来说,std::unique_lock更加灵活,std::unique_lock不拥有与其关联的mutex.构造函数的第二个参数可以指定为std::defer_lock,这样表示在构造unique_lock时,传入的mutex保持unlock状态.然后通过调用std::unique_lock对象的lock()方法或者将将std::unique_lock对象传入std::lock()方法来锁定mutex. #include <mutex> class some