线程
线程,有时被称为轻量进程,是程序执行的最小单元。
C++11线程:
我们知道平常谈C++线程相关的东东基本都是基于之后要学习的posix相关的,其实在C++11有自己新式创建线程的方法,所以先来看一下,看在C++11中如何来创建一个线的,如下:
比较简单,直接过~
POSIX线程【常用】:
POSIX 可移植操作系统接口,标准定义了操作系统应该为应用程序提供的接口标准。相比C++ 11的线程这种方式就要麻烦一些,下面来用看一下它的创建方式:
下面来使用一下:
然后第三个参数则为线程运行的函数地址,其参数类型为:
也就是是个函数指针,该函数返回一个void*指针,接收一个void*参数,所以咱们来定义一下该参数:
最后一个参数则是运行函数的参数,所以咱们也来定义一下:
编译运行:
- 线程属性:
线程具有属性,用 pthread_attr_t 表示,目前咱们在创建线程时这块的参数是传的0,如下:
接下来咱们来定义一下该参数:
那设置该属性有啥用呢,主要是有以下两种用处,如下:
①、分离线程【了解既可】:线程创建默认是非分离的,当pthread_join()函数返回时,创建的线程终止,释放自己占用的系统资源
分离线程是指不能被其他线程等待,pthread_join无效,线程自己玩自己的。
在默认属性的情况下我们的join()是能正常等到线程结束之后才结束的,如下:
接下来设置成分离线程属性,如下:
再来编译运行:
②、调度策略与优先级【实际用得少,了解既可】:
可以看到我们的join()已经不起作用了,这个了解一下既可,在Android NDK中实际中基本上很少用到它。
这里直接贴出用法: - 线程同步:
多线程同时读写同一份共享资源的时候,可能会引起冲突。需要引入线程“同步”机制,即各位线程之间有序地对共享资源进行操作。
下面先看下有线程同步问题的代码:
编译运行:
接下来可以给程序加入互斥锁,具体做法如下:
此时再编译运行:
其实跟java的synchronized是一样的效果。
- 条件变量:
条件变量是线程间进行同步的一种机制,主要包括两个动作:一个线程等待"条件变量的条件成立"而挂起;另一个线程使"条件成立",从而唤醒挂起线程。
下面使用它来完成一个生产者消费者模型,也就是如果发现队列里没有数据可消费了则等待生产者进行生产,一旦生产了数据则消费者立马消费,具体实现如下:
接下来实现具体的插入元素及取出元素:
但是!!目前实现还不尽人意,当队列中没有数据时,目前方法是直接就结束,并没有一个等待的过程,在Java中我们可以使用wait和notify机制达到,而在C++中可以使用条件变量来搞定,具体做法如下:
同样的条件变量也是需要初始化和销毁的,下面来使用它:
接下来咱们来测试一下:
编译运行:
注意:条件变量等待会让cpu释放锁,而不是一直在背后运行,能提高性能。
智能指针
自C++11起,C++标准库提供了两大类型的智能指针。为啥要有智能指针呢?下面先来用程序来说明一下背景:
但是!!
对于堆上的内存都需要我们手动去释放内存,如下:
也就是在实际开发中会存在堆中申请的内存可能会忘记手动释放而造成内存泄漏,所以智能指针的出现就是用来解决这个问题的。
shared_ptr:
操作引用计数实现共享式拥有的概念。多个智能指针可以指向相同的对象,这个对象和其相关资源会在最后一个被销毁时释放。
下面咱们来使用一下它:
其实它内部是实现了引用计数,像下面这个程序:
而当testPtr()方法执行完之后,shared_ptr1和shared_ptr2都会回收,因为是栈上的内在,所以当shared_ptr1被回收时其引用计数由2就会变为1,同样的shared_ptr2被回收时其引用计数就由1又变回了0,而当引用计数为0时则会delete到所引用的A对象了。
注意:虽然使用shared_ptr能够非常方便的为我们自动释放对象,但是还是会出现一些问题。最典型的就是循环引用问题。
下面来看一下下面的代码:
结果一切正常,没有内存泄漏,但是如果下面这样来改就会有问题了,如下:
所以基于这个问题,weak_ptr就诞生了。
- weak_ptr:
weak_ptr是为配合shared_ptr而引入的一种智能指针。主要用于观测资源的引用情况。
它的构造和析构不会引起引用记数的增加或减少。没有重载*和->但可以使用lock获得一个可用的shared_ptr对象。
配合shared_ptr解决循环引用问题,下面来对上面有问题的代码进行修改,如下:
注意:weak_ptr 提供expired 方法等价于 use_count == 0,当expired为true时,lock返回一个存储空指针的shared_ptr。
unique_ptr:
实现独占式引用,保证同一时间只有一个智能指针指向内部对象。什么意思?看代码:
自定义智能指针:
为了更加深入的理解“shared_ptr”这个智能指针的原理,咱们动手来写一个类似效果的智能指针,先新建一个头文件定义大体的框架:
还得实现一个拷贝构造函数:
咱们来使用一下:
那如果调用时这样来写呢?
此时就需要重载一下=号运算符了,如下:
编译运行:
下面再来理解一下这段代码,为啥要引用计数要减1,如下:
这时因为:
因为在默认构造中是这么写的:
然后此时要将该对像进行如下新的赋值:
那在赋值之前,不得先将shared_ptr2这个新对象中的值给清掉么,这样说还不太明显,下面来改造一下程序就立马能明白了:
此时shared_ptr2又要引用shared_ptr1,如果不先释放shared_ptr2原来的值,那不就造成了a2有内存泄漏么,试一下:
编译运行:
所以还是将代码还原,再运行:
编译运行:
总之:其实智能指针底层就是用了引用计数,并利用栈内存出方法必然会释放的原因来实现的。
部分C++11、14特性【不一定自己会使用这些特性,但是别人使用到了需要能读懂】
nullptr:
nullptr 出现的目的是为了替代 NULL。 C++11之前直接将NULL定义为 0。
看下代码:
所以为了解决这个问题,可以用nullptr,如下:
类型推导:
C++11 重新定义了auto 和 decltype 这两个关键字实现了类型推导,让编译器来操心变量的类型。
看下代码:
而对于vector的遍历也可以用auto来简化,具体如下:
基于范围的for循环:
实际上就是foreach,新式写法可以这样写了:
Lambda:【懂java8的应该这块不难理解】
匿名函数,即没有函数名的函数
完整形式:
捕获外部变量列表 mutable exception->返回类型 { 函数体 }
mutable:在外部变量列表以值来捕获时,无法修改变量的值,加上mutable表示可修改(不会影响外部变量)
这里以创建线程为例,原来的方式我们已经比较清楚了,看一下使用Lambda表达式简化成了啥样:
其中这块还有一些其它的形式:
如果我们想在线程中引用外部的变量,目前的写法是不行的,如下:
此时如果使用它:
就可以正常引用了,如下:
但是此时如果我们想在线程中去修改外部变量的值呢?
此时另外一种形式就发挥作用了,如下:
修改一下代码:
此外如果方法有值返回可以这样写:
但是注意:这里的"-> auto"自动推导在C++11是不支持的,而在C++14中是支持的。