C++11 新标准中引入了五个头文件来支持多线程编程:
: 该文件主要申明了俩个类,std::atomic 和 std::atomic_flag,另外还声明了一套 C 风格的原子类和与C兼容的原子操作的函数。
: 该文件主要声明了 std::thread 类,另外std::thread 命名空间也在该头文件中。
: 该头文件主要声明了与互斥量(mutex)相关的类,包括 std::mutex 系列类,std::lock_guard, std::unique_lock,以及其他的类型和函数。
:该头文件主要声明了与条件变量相关的类,包括 std::condition_variable 和 std::condition_variable_any。
:该头文件主要声明了 std::promise, std::package_task 两个 Provider 类,以及 std::future 和 std::shared_future 两个 Future 类,另外还有一些与之相关的类型和函数,std::async() 函数就声明在此头文件中。
1.默认构造函数:thread() noexcept;
构造一个任何线程都不执行的线程对象,即空的thread执行对象。
2.初始化函数:
template
explicit thread (Fn&& fn, Args&&... args);
构造一个线程对象,可以开启线程执行,该thread对象可被joinable,新执行的线程调用函数fn,并传递args作为参数:fn
:可以指向函数,指向成员,或是移动构造函数args
:传递给fn的参数,这些参数可以移动赋值构造。如果fn是一个成员指针,那么第一个args参数就必须是一个对象,或是引用,或是指向该对象的指针。
3.拷贝构造函数 (被禁用):thread (const thread&) = delete
,意味着thread不可被拷贝构造。
4.move构造函数 (移动构造函数):thread (thread && x)noexcept
调用成功后x不代表任何thread执行对象。
std::thread::join
该函数返回时,线程执行完成。
当 一个 thread 调用Join方法的时候,MainThread 就被停止执行,直到该 thread 线程执行完毕。
注意:可被joinable的thread对象必须在他们销毁之前被主线程join或者将其设置为detached。
#include
#include // std::thread, std::this_thread::sleep_for
#include // std::chrono::seconds
void pause_thread(int n)
{
std::this_thread::sleep_for (std::chrono::seconds(n));
std::cout << "pause of " << n << " seconds ended\n";
}
int main()
{
std::cout << "Spawning 3 threads...\n";
std::thread t1 (pause_thread,1);
std::thread t2 (pause_thread,2);
std::thread t3 (pause_thread,3);
std::cout << "Done spawning threads. Now waiting for them to join:\n";
t1.join(); //停下主线程,进入t1 线程
t2.join(); //进入t2线程
t3.join(); // 进入t3线程
std::cout << "All threads joined!\n";
return 0;
}
std::thread::detach
分离线程的对象,使他们彼此独立地执行所表示的线程。这俩个线程既没有被阻止,也没有以任何方式同步。当任一结束执行,其资源被释放。
#include // std::cout
#include // std::thread, std::this_thread::sleep_for
#include // std::chrono::seconds
void pause_thread(int n)
{
std::this_thread::sleep_for (std::chrono::seconds(n));
std::cout << "pause of " << n << " seconds ended\n";
}
int main()
{
std::cout << "Spawning and detaching 3 threads...\n";
std::thread (pause_thread,1).detach();
std::thread (pause_thread,2).detach();
std::thread (pause_thread,3).detach();
std::cout << "Done spawning threads.\n";
std::cout << "(the main thread will now pause for 5 seconds)\n";
// give the detached threads time to finish (but not guaranteed!):
pause_thread(5);
return 0;
}
//输出
Output (after 5 seconds):
Spawning and detaching 3 threads...
Done spawning threads.
(the main thread will now pause for 5 seconds)
pause of 1 seconds ended
pause of 2 seconds ended
pause of 3 seconds ended
pause of 5 seconds ended
更多的thread中的join()和detach()例子。
是否一定要加join()或者detach()呢?其实是不一定的! 看下面程序:
#include
#include
#include
using namespace std;
class Test{
public:
void run(int num);
};
void Test::run(int num){
int count=100;
while(count>0){
std::cout<<"aaaa: "<<num<<std::endl;
count--;
}
}
class Test2{
private:
Test* testClass;
public:
int printNum(int inputNum){
cout <<"Test2:"<<inputNum<<endl;
}
void setTestClass(Test* testClassInput){
testClass = testClassInput;
}
};
class System{
private:
Test2* pTest2;
Test* pTest;
public:
System(){
//在主线程运行
pTest2 = new Test2();
//开劈新的线程
pTest = new Test();
std::thread* ptTest = new thread(&Test::run, pTest,1);
pTest2->setTestClass(pTest);
}
void printNumViaTest2(int num){
pTest2->printNum(num);
}
};
int main (){
System test;
for(int i=0; i<1000; i++){
test.printNumViaTest2(i);
}
//usleep(1000);
return 0;
}
在上述程序中,没有调用join() 或者 detach() 函数,但是该程序可以正常运行,主要是因为线程在运行的时候,主进程还未退出。有时候,若不用join() 或者 detach() 可能会出现terminate called without an active exception Aborted
的错误,这是因为线程还在运行的时候,主进程就退出了。
初始化 thread 类的构造函数
对于类的成员函数,我们需要给出类对象的地址:
#include
#include
using namespace std;
class A
{
public:
void fun(int a,int b)
{
std::cout<<"this is a thread"<
std::thread(&A::fun,a,k,k+1); 这个地方就可以看出thread 类的构造对于成员函数的重载了,std::thread t(函数(成员函数)地址,对象地址,成员函数的参数1,参数2,参数3...)。
相比非成员函数,成员函数需要给出类实例化对象的地址,如果该线程是在同一类的某一成员函数当中被构造,则直接用this 关键字代替即可。
1.Mutex系列类:
std::mutex,最基本的Mutex类
std::recursive_mutex,递归Mutex类
std::time_mutex,定时Mutex类
std::recursive_timed_mutex,定时递归Mutex类
2.Lock类
std::lock_guard,与Mutex RAII相关,方便线程对互斥量上锁。
std::unique_lock,与Mutex RAII相关,方便线程对互斥量上锁,但提供了更好的上锁和解锁控制。
3.函数
std::try_lock,尝试同时对多个互斥量上锁。
std::lock,可以同时对多个互斥量上锁。
std::call_one,如果多个线程需要同时调用某个函数,call_once可以保证多个线程对该函数只调用一次。
std::mutex 介绍
std::mutex 是C++11中最基本的互斥量,std::mutex对象提供了独占所有权的特性—即不支持递归地对std::mutex对象上锁,而std::recursize_lock则可以递归地对互斥量对象上锁。
std::mutex 成员函数
1.构造函数,std::mutex不允许拷贝构造,也不允许move拷贝,最初产生的mutex对象是unlocked状态的。
2.lock(), 调用线程将锁住该互斥量。线程调用该函数会发生下面3种情况: (1) 如果该互斥量当前没有被锁住,则调用线程将该互斥量锁住,直到调用unlock之前,该线程一直拥有该锁。(2) 如果当前互斥量被其他线程锁住,则当前的调用线程被阻塞。(3) 如果当前互斥量被当前调用线程锁住,则会产生死锁,故lock()后一般需要 unlock()。
3.unlock(), 解锁,释放对互斥量所有权。
4.try_lock(), 尝试锁住互斥量,如果互斥量被其他线程占有,则当前线程也不会被堵塞。出现3种情况:(1) 如果当前互斥量没有被其他锁住,则锁住该互斥量。 (2) 如果当前互斥量被其他线程锁住,则当前调用线程返回false,不会堵塞。(3)如果被当前线程锁住了但没有释放,产生死锁。
加锁和解锁问题
因为我们创造的每一个线程只要在一个进程内,都是共享内存池的,这样在读写数据可能会发生混乱。
C++11提供了mutex类进行加锁和解锁。
#include
#include
#include
std::mutex mut;
class A{
public:
volatile int temp;
A(){
temp=0;
}
void fun(int num){
int count=10;
while(count>0){
mut.lock();
temp++;
std::cout<<"thread_"<
volatile和const关键很相似,都是修饰变量的,只是二者功能不一样。
volatile在多线程当中经常使用,因为在某一线程多次调用某一个变量,编译器会进行优化,将该变量存放在在寄存器当中,(在程序运行时,根据需要到内存中相应的存储单元中调用,如果一个变量在程序中频繁使用,例如循环变量,那么,系统就必须多次访问内存中的该单元,影响程序的执行效率。因此,C\C++语言还定义了一种变量,不是保存在内存上,而是直接存储在CPU中的寄存器中,这种变量称为寄存器变量。)
不会每次都从内存当中读入。如果该变量同时在其他线程当中被修改,这样就会发生读取错误。而加上volatile修饰,则会提醒编译器,这个变量可能会被改变,不能存放到寄存器当中,需要每次都从内存当中读取。
try_lock():
#include // std::cout
#include // std::thread
#include // std::mutex
volatile int counter(0); // non-atomic counter
std::mutex mtx; // locks access to counter
void attempt_10k_increases() {
for (int i=0; i<10000; ++i) {
if (mtx.try_lock()) { // only increase if currently not locked:
++counter;
mtx.unlock();
}
}
}
int main (int argc, const char* argv[]) {
std::thread threads[10];
for (int i=0; i<10; ++i)
threads[i] = std::thread(attempt_10k_increases);
for (auto& th : threads) th.join();
std::cout << counter << " successful increases of the counter.\n";
return 0;
}
std::recursive_mutex 介绍std::recursive_mutex
与 std::mutex
一样,也是一种可以被上锁的对象,但是和 std::mutex
不同的是,std::recursive_mutex
允许同一个线程对互斥量多次上锁(即递归上锁),来获得对互斥量对象的多层所有权,std::recursive_mutex
释放互斥量时需要调用与该锁层次深度相同次数的 unlock()
,可理解为 lock()
次数和 unlock()
次数相同,除此之外,std::recursive_mutex
的特性和 std::mutex
大致相同。
std::time_mutex 介绍std::time_mutex
比std::mutex
多了俩个成员函数,try_lock_for()
,try_lock_until()
。try_lock_for
函数接受一个时间范围,表示在这一段时间范围之内线程如果没有获得锁则被阻塞住(与std::mutex
的try_lock()
不同,try_lock
如果被调用时没有获得锁则直接返回false
),如果在此期间其他线程释放了锁,则该线程可以获得对互斥量的锁,如果超时(即在指定时间内还是没有获得锁),则返回false。
try_lock_until
函数则接受一个时间点作为参数,在指定时间点未到来之前线程如果没有获得锁则被阻塞住,如果在此期间其他线程释放了锁,则该线程可以获得对互斥量的锁,如果超时(即在指定时间内还是没有获得锁),则返回 false
。
#include // std::cout
#include // std::chrono::milliseconds
#include // std::thread
#include // std::timed_mutex
std::timed_mutex mtx;
void fireworks() {
//等待加锁: each thread prints "-" every 200ms:
while (!mtx.try_lock_for(std::chrono::milliseconds(200))) {
std::cout << "-";
}
// 获得锁! - wait for 1s, then this thread prints "*"
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
std::cout << "*\n";
mtx.unlock();
}
int main ()
{
std::thread threads[10];
// spawn 10 threads:
for (int i=0; i<10; ++i)
threads[i] = std::thread(fireworks);
for (auto& th : threads) th.join();
return 0;
}
std::lock_guard 介绍
#include // std::cout
#include // std::thread
#include // std::mutex, std::lock_guard
#include // std::logic_error
std::mutex mtx;
void print_even (int x) {
if (x%2==0) std::cout << x << " is even\n";
else throw (std::logic_error("not even"));
}
void print_thread_id (int id) {
try {
// using a local lock_guard to lock mtx guarantees unlocking on destruction / exception:
std::lock_guard lck (mtx);
print_even(id);
}
catch (std::logic_error&) {
std::cout << "[exception caught]\n";
}
}
int main (){
std::thread threads[10];
// spawn 10 threads:
for (int i=0; i<10; ++i)
threads[i] = std::thread(print_thread_id,i+1);
for (auto& th : threads) th.join();
return 0;
}
std::unique_lock 介绍
与 Mutex RAII 相关,方便线程对互斥量上锁,但提供了更好的上锁和解锁控制。
#include // std::cout
#include // std::thread
#include // std::mutex, std::unique_lock
std::mutex mtx; // mutex for critical section
void print_block (int n, char c) {
// critical section (exclusive access to std::cout signaled by lifetime of lck):
std::unique_lock lck (mtx); //在lck的生命周期内对mtx尝试上锁,lck生命周期结束时自动解锁
for (int i=0; i
使用条件变量来实现生产者/消费者模型:
一种重要的模型,基于等待/通知机制。生产者/消费者模型描述的是有一块缓冲区作为仓库,生产者可将产品放入仓库,消费者可以从仓库中取出产品,生产者/消费者模型关注的是以下几个点:
一共有三种关系:生产者与生产者的互斥关系,消费者与消费者的互斥关系,生产者与消费者的互斥且同步关系。
std::condition_variable
是条件变量,当std::condition_variable
对象的某个wait函数被调用的时候,它使用std::unique_lock
(通过std::mutex
)来锁住当前线程。当前线程会一直被堵塞,直到另一个线程在相同的std::condition_variable
对象上调用了notification
(有notify_all()
和 notify_one()
)函数来唤醒当前线程。
在消费者生产者模型中,如果消费者法线队列中没有东西,就可以让自己休眠,但是不能一直不干活呀,通过notify_one()
就是唤醒处于wait
中一个变量或者通过判断队列是否为空来实现自身唤醒。
这里解释下为啥在管理互斥锁的时候,使用的是std::unique_lock
而不是std::lock_guard
,事实上也不能使用std::lock_guard
,这里需要解释下wait()
函数所做的事情。可以看到,在wait()
函数之前,使用互斥锁保护了,如果wait
的时候什么都不做,会一直持有互斥锁,那么不能够将数据放入队列中 (看例子2)。所以,wait()
函数会先调用互斥锁的unlock()
函数,然后再将自己睡眠,在被唤醒后,又会继续持有锁,保护后面的队列操作。而lock_guard
没有lock
和unlock
接口,而unique_lock
提供了。这就是必须使用unique_lock
的原因。
例子1如下:
#include // std::cout
#include // std::thread
#include // std::mutex, std::unique_lock
#include // std::condition_variable
std::mutex mtx; // 全局互斥锁.
std::condition_variable cv; // 全局条件变量.
bool ready = false; // 全局标志位.
void do_print_id(int id)
{
std::unique_lock <std::mutex> lck(mtx);
while (!ready) // 如果标志位不为 true, 则等待...
cv.wait(lck); // 当前线程被阻塞, 当全局标志位变为 true 之后,
// 线程被唤醒, 继续往下执行打印线程编号id.
std::cout << "thread " << id << '\n';
}
void go()
{
std::unique_lock <std::mutex> lck(mtx);
ready = true; // 设置全局标志位为 true.
cv.notify_all(); // 唤醒所有线程.
}
int main()
{
std::thread threads[10];
// spawn 10 threads:
for (int i = 0; i < 10; ++i)
threads[i] = std::thread(do_print_id, i);
std::cout << "10 threads ready to race...\n";
go(); // go!
for (auto & th:threads)
th.join();
return 0;
}
执行结果如下:
threads ready to race...
thread 1
thread 0
thread 2
thread 3
thread 4
thread 5
thread 6
thread 7
thread 8
thread 9
在例子1中使用了cv.wait(lck)
的写法;换一种写法,wait()
的第二个参数可以传入一个函数表示检查条件,这里使用lambda
函数最为简单,如果这个函数的返回是true
,wait()
函数不会被阻塞会直接返回,如果这个函数返回的是false
,wait()
函数就会堵塞着等待唤醒,如果被伪唤醒,会继续判断函数返回值。
例子2如下:
#include
#include
#include
#include
#include
std::deque<int> q;
std::mutex mu;
std::condition_variable cond;
void producer() {
int count = 10;
while (count > 0) {
std::unique_lock<std::mutex> locker(mu);
q.push_front(count);
locker.unlock();
cond.notify_one(); // Notify one waiting thread, if there is one.
std::this_thread::sleep_for(std::chrono::seconds(1));
count--;
}
}
void customer() {
int data = 0;
while ( data != 1) {
std::unique_lock<std::mutex> locker(mu);
//lambda语法
cond.wait(locker,[&]{return !q.empty();}); // Unlock mu and wait to be notified
data = q.back();
q.pop_back();
locker.unlock();
std::cout << "t2 got a value from t1: " << data << std::endl;
}
}
int main() {
std::thread t1(producer);
std::thread t2(customer);
t1.join();
t2.join();
return 0;
}
执行结果如下:
t2 got a value from t1: 10
t2 got a value from t1: 9
t2 got a value from t1: 8
t2 got a value from t1: 7
t2 got a value from t1: 6
t2 got a value from t1: 5
t2 got a value from t1: 4
t2 got a value from t1: 3
t2 got a value from t1: 2
t2 got a value from t1: 1
std::future
可以用来获取异步任务的结果,因此可以把它当成一种简单的线程间同步手段。std::future
通常由某个Provider创建,可以把Provider想象成一个异步任务的提供者,Provider在某个线程中设置共享状态的值,与该共享状态相关联的std::future
对象调用get (通常在另外一个线程中) 获取该值,如果共享状态的标志不为ready,则调用std::future::get
会阻塞当前的调用者,知道Provider设置了共享状态的值(此时共享状态的标志变为ready),std::future::get
返回异步任务的值或异常(如果发生了异常)。
一个有效(valid)的std::future
对象只有在有效(valid)的情况下才有用,由std::future
默认构造函数创建的future
对象不是有效的(除非当前非有效的future
对象被move
赋值另一个有效的future
对象)。
在一个有效的std::future
对象上调用get
会阻塞当前的调用者,直到Provider设置了共享状态的值或异常(此时共享状态的标志变为ready),std::future::get
将返回异步任务的值或异常(如果发生了异常)。std::future
对象通常由以下三种Provider创建,并和某个共享状态相关联。Provider可以是函数或者类,分别是:
std::async函数
std::promise::get_future, get_future为promise类的成员函数。
std::packaged_task::get_future, 此时get_future为packaged_task的成员函数
以下主要讲下由async
创建的std::future
:
1. std::async和std::futurestd::async
创建一个后台线程执行传递的任务,这个任务只要是callable object
均可,然后返回一个std::future
。std::future
存储一个多线程共享的状态,当调用future.get()
是会阻塞直到绑定的task执行完毕。
Example 1:
#include
#include
void task(){
for(int i=0; i<10; i++){
std::cout << "A";
}
}
int main(){
//创建异步任务,任务返回值保存在std::future中
std::future<void> result{std::async(task)};
//执行其他任务
for(int i=0;i<10;i++){
std::cout << "B";
}
for(int j=0;j<10;j++){
std::cout<<"C";
}
//需要异步任务结果的时候,如果没有返回,那么会等待至返回,get操作会阻塞
result.get();
system("pause");
return 0;
}
输出:
BBBBBBBBBBCCCCCCCCCCAAAAAAAAAA
Example 2:
#include
#include
void task(){
for(int i=0; i<10; i++){
std::cout << "A";
}
}
int main(){
std::future<void> result{std::async(task)};
result.get();
for(int i=0;i<10;i++){
std::cout << "B";
}
system("pause");
return 0;
}
输出:
AAAAAAAAAABBBBBBBBBB
2. std::launch::async
上面task返回void,这个结果没有用,我们只是单纯的想等待任务线程结束。
对这种需要还可以用更简单的方法:指定一个launch policy
Example 1:
#include
#include
void task(){
for(int i=0; i<10; i++){
std::cout << "A";
}
}
int main(){
std::future<void> result{std::async(std::launch::async, task)};
for(int i=0;i<10;i++){
std::cout << "B";
}
system("pause");
return 0;
}
输出
BBBBBBBBBBAAAAAAAAAA
在创建async的时指定一个lauch policy,连result.get都可以不用了,不过还是需要把async的返回值赋给result。
Example 2:
#include
#include
void task(){
for(int i=0; i<10; i++){
std::cout << "A";
}
}
int main(){
std::future<void> result{std::async(std::launch::async, task)};
result.get();
for(int i=0;i<10;i++){
std::cout << "B";
}
system("pause");
return 0;
}
输出
AAAAAAAAAABBBBBBBBBB
3.std::launch::deferred
总共有倆种launch policy:
std::launch::async
当返回的future
失效前会强制执行task,即不调用future.get也会保证task的执行std::launch::deferred
仅当调用future.get
时候才会执行task, 如果创建async时不指定launch policy,会默认std::launch::async | std::launch::deferred
,根据情况选一种执行Example 1:
#include
#include
void task() {
for (int i = 0; i < 10; i++) {
std::cout << "A";
}
}
int main() {
std::future<void> result{ std::async(std::launch::deferred,task) };
for (int i = 0; i < 10; i++) {
std::cout << "B";
}
result.get(); //加了.get()
system("pause");
return 0;
}
输出:
BBBBBBBBBBAAAAAAAAAA
Example 2:
#include
#include
void task(){
for(int i=0; i<10; i++){
std::cout << "A";
}
}
**Example 1:**
int main(){
std::future<void> result{std::async(std::launch::deferred, task)}; //没有调用.get()
for(int i=0;i<10;i++){
std::cout << "B";
}
system("pause");
return 0;
}
输出:
BBBBBBBBBB
Another example:
#include
#include
#include
#include
#include
#include
#include
using namespace std;
int mythread() //线程入口函数
{
cout << "mythread start" << "threadid= " << std::this_thread::get_id() << endl; //打印线程id
std::chrono::milliseconds dura(5000); //定一个5秒的时间
std::this_thread::sleep_for(dura); //休息一定时常
cout << "mythread end" << "threadid= " << std::this_thread::get_id() << endl; //打印线程id
return 5;
}
int main()
{
cout << "main" << "threadid= " << std::this_thread::get_id() << endl;
//std::future result = std::async(mythread);//流程并不卡在这里,让程序决定是否开启新的线程
std::future<int> result = std::async(std::launch::async, mythread);//开启新的线程,mythread和mainthread的线程id是不同的。
cout << "continue....." << endl;
//枚举类型
std::future_status status = result.wait_for(std::chrono::seconds(0));//等待一秒
if (status == std::future_status::deferred)
{
//线程被延迟执行了,系统资源紧张
cout << result.get() << endl; //此时采取调用mythread()
}
else if (status == std::future_status::timeout)//
{
//超时:表示线程还没执行完;我想等待你1秒,希望你返回,你没有返回,那么 status = timeout
//线程还没执行完
cout << "超时:表示线程还没执行完!" << endl;
}
else if (status == std::future_status::ready)
{
//表示线程成功返回
cout << "线程成功执行完毕,返回!" << endl;
cout << result.get() << endl;
}
cout << "I love China!" << endl;
return 0;
}
更深入的如std::atomic
请移步https://www.cnblogs.com/haippy/p/3284540.html,非常好的C++11并发的版块,也mark一下,以供后面继续学习。