标准库多线程编程 – 基础

DinS          Written on 2017/9/4

本文开始讲解标准库里提供的多线程编程工具,开始前请先阅读windows多线程专题,重要的是理解线程、互斥和同步的概念。

一、概述

但凡讲解线程、进程的内容都是放在操作系统下的,不管是上课还是看书。这说明管理线程、进程本来就是操作系统的事,所以使用操作系统的API做线程是一件很自然的事。
那么在c++语言层面的线程操作是什么地位?线程操作是在c++11标准中加入的,其目的当然是为了提高代码可移植性。如果使用了操作系统的线程API,那么移植到另一个操作系统肯定是要重写的,但是如果使用c++语言层面的线程操作,可移植性会大大增强。

我们应该使用哪种方式操作线程呢?还是看需求。如果你肯定你的软件只在特定操作系统运行,那么应该使用操作系统提供的API。(但是在现代软件环境下,这种事很少发生)如果你认为你的程序将要移植,那么最好使用c++语言层面的线程操作。
另外二者的侧重点不太一样,随着之后的讲解会更加深入这一点。在本文的最后会对两种编程方式做一个综合对比,得出一个结论。

本文会经常与《windows多线程编程》专题做对比,所以建议先阅读那个专题,了解线程操作的基本概念,然后阅读本专题。
c++11提供了五个头文件做线程操作,通常会以这个为区分标准讲解。但是本专题将以多线程的概念为标准,在不同情况引入不同模块讲解,这样的效果应该更好。

二、基本线程操作

c++语言层面的线程操作比windows操作系统API简单。
首先看一段代码:

运行结果

我们写了一个函数,然后在main里使用thread类派生一个线程。整个代码很简洁,需要注意的有两点:
第一,关于this_thread。其实thread类本身有get_id这个函数,但是问题是我们写的用于派生新线程的函数不知道关于thread的信息,所以c++提供了this_thread方便操作。
第二,关于join。为什么join是等待线程结束?从语义上来说,可以从下图理解

可以这样理解,开辟一个新线程,相当于从main中分离出一条线thread。那么当分离的这条线重新并入main时,等于线程结束。这个并入就是join的含义,就好像江河的支流那样。

如何开辟多条线程并传入参数呢?如何传入多个参数以及引用呢?
看下面代码:

运行结果

输出是混乱的,这不意外,因为没有做线程间通信处理。
我们首先做了一个数组,用for循环开辟线程,存储数组,然后再次用for等待数组中所有线程结束。
传参则更为简单,直接在构造函数中传即可。
传入引用稍微显得有点复杂,出现了一个ref(),实际上ref跟&表达的意思一样,设计ref是为了配合bind()使用。具体的底层可以不去深究,掌握了这种用法就OK了。

三、与windowsAPI对比

细心的读者会发现,这里做的示例与《windows多线程编程》异曲同工,实际上接下来的也会这样做,方便对比。
那么现在我们就来对比一下c++线程和windows线程的异同,可以打开《windows多线程编程-基础》进行代码对比。

1.线程函数书写

windows下子线程函数的写法是固定的,必须是__stdcall,而且参数只能是void*
c++更加灵活和简便,而且能够传入多个参数,不必借助结构体和令人费解的void*强制转换

2.开辟线程的操作

使用_beginthreadex可以提供更加精准的控制。
不过对于一般使用来讲,使用c++线程构造函数足矣。

3.等待线程结束

winsowsAPI显然要更优秀,一句代码还能够设定等待时限。
相比之下c++使用for循环等待略显复杂,并且不能设定时限。当然,等待时限问题必然是可以在c++层面解决的,看后文即可知晓

接下来将研究互斥问题,见《标准库多线程编程 – 互斥问题》。

 

参考资料:http://www.cnblogs.com/haippy/p/3284540.html