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

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

多线程编程的好处

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

C++11并发编程_第1张图片

C++11并发编程_第2张图片

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

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

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

如何启动一个线程

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

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

#include 
#include 

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

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

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

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

[root@xiaojianping c++]# g++ -std=c++11 -lpthread -o a a.cpp
[root@xiaojianping c++]# ./a
Hello world from 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 
#include 

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

    return 0;
}

[root@xiaojianping c++]# ./b

Hello world from thread                !

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

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

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

#include 
#include 


//线程执行函数接受两个int类型参数
void hello(int x,int y)
{
   std::cout<<"Hello world from thread !" <[root@xiaojianping c++]# ./c
Hello 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 
#include 
#include 

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

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

   return 0;
}
[root@xiaojianping 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
[root@xiaojianping 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 
#include 
#include 

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 threads;
    for(int i = 0; i < 6; ++i)
    { 
        threads.push_back(std::thread(hello)); 
    }
    for(auto& thread : threads)
    {
        thread.join();
    }

   return 0;
}

 

[root@xiaojianping 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
[root@xiaojianping 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程序的时候,就不会乱了。

转载了码农有道的微信公众号,并把后部分完成了。