对于并发编程,首先应该从线程说起,对于线程的基本概念就不多说了,有不清楚的同学可以查查相关资料。这里就从< thread>开始看起,先对C++11的线程有个大致了解。

std::thread

一个thread对象也许没有表示任何线程,比如在默认构造后、move from后、detach、join后;一个在运行的线程也不一定有与之关联的thread对象,比如在detach后。不会出现两个thread对象表示着同一个执行的线程,于是thread是不可拷贝构造和拷贝赋值的,但是可以移动构造和移动赋值。

Hello World

std::thread的用法与pthread的类似,先从一个简单的例子看起:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>
#include <thread>


int main(int argc, char** argv) {

std::thread t{

[](){

std::cout << "Hello World!" << std::endl;

}

}



t.join();

return 0;

}

这里使用clang来编译,注意要加上 -std=c++11

1
2

clang -std=c++11 -o helloWorld helloWorld.cpp

这段代码里使用一个打印hello world的lambda表达式作为参数构造一个thread(当然,传递一个函数(指针)也是没有问题的),然后调用join()等待线程运行完毕。

成员函数

  • 构造函数
1
2
3
4
5
6
7
8
9
10

thread( ); // (1) 默认构造

thread( thread&& other ); // (2) 移动构造

template< class Function, class... Args >

explicit thread( Function&& f, Args&&... args ); // (3) 最常用的构造方法

thread( const thread& ) = delete; // (4) 拷贝构造设置为delete的

1) 创建了一个thread对象,没有表示任何线程

2) 移动构造函数。创建一个表示other表示的线程的thread对象,构造完成后other不在表示之前的线程了

3) 创建一个表示一个运行线程的thread对象。首先,构造函数通过使用下面的函数拷贝/移动所有的参数(连同函数f和它的参数一起)到线程可以访问的存储区:

1
2
3
4
5
6
7
8

template <class T>

typename decay<T>::type decay_copy(T&& v) {

return std::forward<T>(v);

}

需要注意的是在此期间抛出的所有异常都会抛在当前线程,而不是新创建的线程里。

下面来看下新的线程将会怎么去处理传入的参数。我们设value_args(包含函数和它参数一起的参数)为t1,t2,…tN,N为sizeof…(value_args),value_args是通过调用上面的decay_copy得到的。然后会按照下面的规则来执行代码:

1) 如果f是类T的成员函数的指针,就会执行f,会忽略掉返回值。代码实际上是这样执行的:

1
2
3
4
5
6
7
8

// 如果t1的类型是T,或者T的引用,或者从T派生出来类型的引用

(t1.*f) (t2, ... , tN)

// 否则

((*t1).*f) (t2, ..., tN)

2) 如果N == 1,f是一个类的成员数据对象(member data object),则它会被访问。但是这个对象的值会被忽略。代码实际上是这样执行的:

1
2
3
4
5
6
7
8

// 如果t1是T或者T的引用或者从T派生出来类型的引用

t1.*f

// 否则

(*t1).*f

3) 否则的话,f会被当做一个非成员函数来调用,返回值会被忽略。代码实际上是这样执行的:

1
2

f(t1, t2, ..., tN)

4) 拷贝构造函数是delete的,线程是不可拷贝的。没有两个thread对象表示着同一个线程。

  • Observers
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

//检查这个thread对象是否是有效的执行线程。如果get_id() != std::thread::id()就会返回true。默认构造的thread对象不是joinable 的。

//如果一个线程执行完毕,但还没有被join,它依然被认为是有效的执行线程

bool joinable() const;

// 返回当前线程的id

std::thread::id get_id() const;

//返回native_handle

native_handle_type native_handle();

// 返回当前电脑所能支持的并发线程数量

static unsigned hardware_concurrency();
  • Operations
1
2
3
4
5
6
7
8
9
10
11
12
13
14

// 阻塞当前线程,直到*this线程执行完毕

void join();

//将执行的线程与thread对象分离,允许线程独立的执行。任何分配的资源都会在线程退出时被释放

// 在调用detach后,*this不在拥有任何线程

void detach();

// 交换两个线程

void swap();

namespace this_thread

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

// 将当前线程挂起,等待下次调度,让其他线程运行

void yield();

// 返回当前线程的id

std::thread::id get_id()

// 阻塞当前线程的执行一定时间,时间由sleep_duration指定

template<class Rep, class Period>

void sleep_for(const std::chrono::duration<Rep, Period>& sleep_duration);

// 阻塞当前线程直到某个时间点,时间点由sleep_time指定。

template< class Clock, class Duration >

void sleep_until( const std::chrono::time_point<Clock,Duration>& sleep_time );

最近在做C++并发编程相关的东西,看到C++11加入了不少好用的东西,就写点博客记录下来。

C++11 加入多线程支持

C++11标准开始支持多线程编程,在之前多线程都需要系统支持,比如*nix下的pthread相关。这次主要提供了以下几个头文件:

  • < thread>:包含了std::thread类,以及this_thread命名空间下的yield(),get_id()等方法

  • < mutex>:包含了几种互斥锁,比如mutex,shared_mutex等;以及相关的方法

  • < condition_variable>:包含了条件变量相关的类

  • < future>://这个应该是对异步操作的支持,后面看了再回来补充

  • < atomic> :包含std::atomic和std::atomic_flag等,还有memory_order相关

已完成的记录索引

C++ template机制自身是图灵完备的:它可以被用来计算任何可计算的值。于是就有了模板元编程(template metaprogramming),可以编写出”在C++编译期间执行并在编译结束时停止”的程序。
这几篇博客主要用来记录一些模板的基础知识,后面再写一些关于模板元编程的东西。

关于typename的另一种意义

一般的,在以下的声明中,class和typename是没有什么不同的:

1
2
template<class T> class Foo;
template<typename T> class Foo;

然而C++并不总是把class和typename视为等价。有时候必须要使用typename。我们先来看一个例子,对于下面的函数foo,接收一个C类型的容器:

1
2
3
4
5
template<typename C>
void foo(const C& container) {
C::const_iterator* x;
...
}

看起来我们好像声明了一个本地变量x,它是一个指向C::const_iterator类型的指针。我们之所以这样认为是因为我们已经知道C::const_iterator是个类型。但如果C::const_iterator不是一个类型呢?如果C刚好有一个static成员变量被命名为const_iterator,而x碰巧是个global变量名字呢?这样的话上面的代码就是一个相乘动作:C::const_iterator乘以x。这完全是有可能的。
在我们知道C是什么类型之前,是没有办法知道C::const_iterator是否是一个类型的。编译器开始解析template foo的时候,也不知道C是什么,C++有个规则可以解析(resolve)这个歧义状态:如果解析器在template中遭遇一个嵌套从属名称时,它就假设这个名称不是一个类型,除非你告诉它是。所以默认情况下嵌套从属名称不是类型。这个规则有个例外,后面会提到。
刚刚出现了一个名词”嵌套从属名称”,现在来解释一下:template内出现的名称如果依赖于某个template参数,就称之为从属名称(dependent name)。如果从属名称在class内成嵌套状,就称他为嵌套从属名称(nested dependent name)。C::const_iterator就是一个从属名称,而且还是个嵌套从属类型名称(nested dependent type name),也就是个嵌套从属名称并且是某个类型。
对于上面的代码,如果希望它按我们的意愿去表达,就需要告诉编译器C::const_iterator是一个类型。只要在它前面加上关键字typename即可:

1
2
3
4
template<typename C>
void foo(const C& container) {
typename C::const_iterator *x;
}

一般性规则很简单:任何时候当你想要在template中使用一个嵌套从属类型名称时,就必须在紧邻它之前的位置加上关键字typename。这个规则的例外是typename不可以出现在base classes list内的嵌套从属类型名称之前,也不能在member initialization list(成员初始列表)中出现。例如:

1
2
3
4
5
6
7
8
9
template<typename T>
class Derived: public Base<T>::Nested { //不允许typename
public:
explicit Derived(int x)
: Base<T>::Nested(x) { //不允许typename
typename Base<T>::Nested tmp; //需要加上typename
...
}
};

简而言之

  • 在声明template参数时,前缀关键字class和typename是没有区别的。
  • 是在用嵌套从属类型名称时需要使用关键字typename来标识;但不要在基类列表和成员初始化列表内使用typename修饰基类。

在老版C++中,临时变量(称为右值”R-values”)经常用作交换两个变量。比如下面例子中的tmp变量,这个函数需要传递两个string引用,但在交换的过程中需要一个临时的对象tmp,这样就造成了对象的构造,内存的分配还有对象的拷贝构造等动作,成本比较高。

1
2
3
4
5
void Swap(string &a, string &b) {
string tmp = a;
a = b;
b = tmp;
}

C++ 11增加了一个新的引用类型–右值引用,通过typename &&来定义。他们能够以non-const值的方式传入,允许对象去修改他们。
在上面的例子中,string保存了一个动态内存分配的char指针,如果一个string对象发生拷贝构造,只能构造一个新的临时对象,并把源对象的内存拷贝到新的对象里,然后销毁临时对象及其内存。这样会造成性能的问题。
通过右值引用,string的构造函数需要改成”move构造函数”,如下所示。这样的话,对某个string的右值引用就可以单纯的从右值复制其内部的char
指针到新的string,然后留下空的右值。这个操作不需要内存数组的复制,而且空的暂时对象的析构并不会释放内存,这样就提高了效率。

1
2
3
4
class string {
string (string&&);
string&& operator = (string&&);
};

std::move

定义在头文件中:

1
2
template< typename T >
typename std::remove_reference<T>::type&& move( T&& t ) noexcept ; //(C++11 - C++14)

1
2
template< typename T >
constexpr typename std::remove_reference<T>::type&& move( T&& t ) noexcept ; //(C++14 - )

std::move返回指向实参的右值引用,并将实参转化为将亡值(xvalue, eXpiring Value)。

std::forward转发

定义在头文件中:

1
2
template< typename T >
T&& forward( typename std::remove_reference<T>::type& t ) noexcept ; //(1) (C++11 - C++14)

1
2
template< typename T >
constexpr T&& forward( typename std::remove_reference<T>::type& t ) noexcept ; //(1) (C++14 - )
1
2
template< typename T >
T&& forward( typename std::remove_reference<T>::type&& t ) noexcept ; // (2) (C++11 - C++14)
1
2
template< typename T >
constexpr T&& forward( typename std::remove_reference<T>::type&& t ) noexcept ; //(2) (C++14 - )

TODO

Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub.

Quick Start

Create a new post

1
$ hexo new "My New Post"

More info: Writing

Run server

1
$ hexo server

More info: Server

Generate static files

1
$ hexo generate

More info: Generating

Deploy to remote sites

1
$ hexo deploy

More info: Deployment

1
printf("Hello world\n");