C++线程

创建线程

在C++11之前,C++没有对线程提供语言级别的支持,各种操作系统和编译器实现线程的方法不一样。
C++11增加了线程以及线程相关的类,统一编程风格、简单易用、跨平台。

创建线程

头文件#include<thread>
线程类std::thread
构造函数:

  1. thread() noexcept
    默认构造函数,构造一个线程对象,不执行任何任务(不会启动子线程)

  2. 常用99%
    template<class Function,class... Args>
    explicit thread(Function&& fx,Args&&... args);
    创建线程对象,在线程中执行任务函数fx中的代码,args是要传递给任务函数fx的参数。
    任务函数fx可以是普通函数、类的非静态成员函数、类的静态成员函数、匿名函数、仿函数。
    例如:

    #define _CRT_SECURE_NO_WARNINGS
    #include <iostream>
    #include <vector>
    #include <cmath>
    #include <fstream>//ifstream 类需要包含的头文件
    #include<string>
    #include<algorithm>
    #include<cassert>
    #include<initializer_list>
    #include<chrono>
    #include<iomanip>
    #include<Windows.h>//Sleep()函数需要这个头文件
    using namespace std;
    int main() {
    	cout << "任务完成" << endl;
    	for (int i = 0;i < 10;i++) {
    		cout << "执行任务中.....\n";
    		Sleep(1000);//执行任务需要时间
    	}
    	cout << "任务完成" << endl;
    }
    

    这个demo程序是典型的单任务模式,他的特点是必须在完成一个任务之后才能去执行其他任务。如果在当前任务由间插入其它的任务,那么,当前任务就会被推后。如果想在同一时间执行多个任务,怎么办?可以同多线程
    然后这是简单是实现

    #define _CRT_SECURE_NO_WARNINGS
    #include <iostream>
    #include <vector>
    #include <cmath>
    #include <fstream>//ifstream 类需要包含的头文件
    #include<string>
    #include<algorithm>
    #include<cassert>
    #include<thread>
    #include<initializer_list>
    #include<chrono>
    #include<iomanip>
    #include<Windows.h>//Sleep()函数需要这个头文件
    using namespace std;
    void func(int bh, const string& str) {
    	for (int i = 1;i <= 10;i++) {
    		cout << "第" << i << "次表白:亲爱的" << bh << "号," << str << endl;
    		Sleep(1000);
    	}
    }
    int main() {
    	thread t1(func,3,"我是一只小小鸟");//创建线程对象
    
    	cout << "任务完成" << endl;
    	for (int i = 0;i < 10;i++) {
    		cout << "执行任务中.....\n";
    		Sleep(1000);//执行任务需要时间
    	}
    	cout << "任务完成" << endl;
    	t1.join();//回收线程t1的资源
    }
    

    在这里插入图片描述
    然后这个demo函数就是一个多线程程序了,main函数中的代码叫主程序,主线程,或者主进程,对象t1叫子线程,主线程只有一个,子线程可以很多,与计算机的硬件资源有关。硬件越好,就可以创建更多的子线程。普通的电脑可以创建几百个子线程,好的服务器可以创建。
    然后我们对上面的代码再创建一个线程:

    #define _CRT_SECURE_NO_WARNINGS
    #include <iostream>
    #include <vector>
    #include <cmath>
    #include <fstream>//ifstream 类需要包含的头文件
    #include<string>
    #include<algorithm>
    #include<cassert>
    #include<thread>
    #include<initializer_list>
    #include<chrono>
    #include<iomanip>
    #include<Windows.h>//Sleep()函数需要这个头文件
    using namespace std;
    void func(int bh, const string& str) {
    	for (int i = 1;i <= 10;i++) {
    		cout << "第" << i << "次表白:亲爱的" << bh << "号," << str << endl;
    		Sleep(1000);
    	}
    }
    int main() {
    	thread t1(func,3,"我是一只小小鸟");//创建线程对象
    	thread t2(func, 8, "我是一只傻傻鸟");
    
    	cout << "任务完成" << endl;
    	for (int i = 0;i < 10;i++) {
    		cout << "执行任务中.....\n";
    		Sleep(1000);//执行任务需要时间
    	}
    	cout << "任务完成" << endl;
    	t1.join();//回收线程t1的资源
    	t2.join();//回收线程t2的资源
    }
    

    在这里插入图片描述
    上面是用普通函数创建线程,也就是thread的第一个参数是普通函数,其实除了普通函数,也可以用类的非静态成员函数,类的静态成员函数,lambda函数,仿函数。

    #define _CRT_SECURE_NO_WARNINGS
    #include <iostream>
    #include <vector>
    #include <cmath>
    #include <fstream>//ifstream 类需要包含的头文件
    #include<string>
    #include<algorithm>
    #include<cassert>
    #include<thread>
    #include<initializer_list>
    #include<chrono>
    #include<iomanip>
    #include<Windows.h>//Sleep()函数需要这个头文件
    using namespace std;
    void func(int bh, const string& str) {
    	for (int i = 1;i <= 10;i++) {
    		cout << "第" << i << "次表白:亲爱的" << bh << "号," << str << endl;
    		Sleep(1000);
    	}
    }
    
    //仿函数
    class mythread1 {
    public:
    	void operator()(int bh, const string& str) {
    		for (int i = 1;i <= 10;i++) {
    			cout << "第" << i << "次表白:亲爱的" << bh << "号," << str << endl;
    			Sleep(1000);
    		}
    	}
    };
    
    //类的静态成员函数
    class mythread2 {
    public:
    	static void func(int bh, const string& str) {
    		for (int i = 1;i <= 10;i++) {
    			cout << "第" << i << "次表白:亲爱的" << bh << "号," << str << endl;
    			Sleep(1000);
    		}
    	}
    };
    //类的普通的成员函数
    class mythread3 {
    public:
    	void func(int bh, const string& str) {
    		for (int i = 1;i <= 10;i++) {
    			cout << "第" << i << "次表白:亲爱的" << bh << "号," << str << endl;
    			Sleep(1000);
    		}
    	}
    };
    
    int main() {
    	//用普通函数创建线程
    	//thread t1(func,3,"我是一只小小鸟");//创建线程对象
    	//thread t2(func, 8, "我是一只傻傻鸟");
    
    	//用lamdba函数创建线程
    	auto f = [](int bh, const string& str) {
    		for (int i = 1;i <= 10;i++) {
    			cout << "第" << i << "次表白:亲爱的" << bh << "号," << str << endl;
    			Sleep(1000);
    		}
    	};
    	//thread t3(f, 8, "我是一只傻傻鸟");
    
    	//仿函数
    	//thread t4(mythread1(), 8, "我是一只傻傻鸟");
    	
    	//类的静态成员函数
    	//thread t5(mythread2::func, 8, "我是一只小小鸟");
    
    	//类的普通的成员函数
    	mythread3 myth;//必须先创建类的对象,必须保证对象的声明周期比子线程要长
    	thread t6(&mythread3::func, &myth,8, "我是一只小小鸟");//第二个参数必须填对象的this指针
    	
    	
    	cout << "任务完成" << endl;
    	for (int i = 0;i < 10;i++) {
    		cout << "执行任务中.....\n";
    		Sleep(1000);//执行任务需要时间
    	}
    	cout << "任务完成" << endl;
    	//t1.join();//回收线程t1的资源
    	//t2.join();//回收线程t2的资源
    	//t3.join();//回收线程t3的资源
    	//t4.join();//回收线程t4的资源
    	//t5.join();
    	t6.join();
    }
    
  3. thread(const thread& ) = delete;
    删除拷贝构造函数,不允许线程对象之间的拷贝。

  4. thread(thread&& other ) noexcept;
    移动构造函数,将线程other的资源所有权转移给新创建的线程对象。

赋值函数:

thread& operator= (thread&& other) noexcept;
thread& operator= (const other&) = delete; 

线程中的资源不能被复制,如果other是右值,会进行资源所有权的转移,如果other是左值,禁止拷贝。

注意:

  • 先创建的子线程不一定跑得最快(程序运行的速度有很大的偶然性)。
  • 线程的任务函数返回后,子线程将终止。
  • 如果主程序(主线程)退出(不论是正常退出还是意外终止),全部的子线程将强行被终止。

线程资源回收

虽然同一个进程的多个线程共享进程的栈空间,但是,每个子线程在这个栈中拥有自己私有的栈空间。所以,线程结束时需要回收资源。
回收子线程的资源有两种方法:

  1. 在主程序中,调用join()成员函数等到子线程推出,回收他的资源,如果子线程已推出,join()函数立即返回,否则会产生堵塞,知道子线程推出。
  2. 在子线程中,调用detach()成员函数分离子线程,子线程退出时,系统自动回收资源。joinable()成员函数可以判断子线程的分离状态,函数返回布尔类型。
    joinable()成员函数可以判断子线程的分离状态,函数返回布尔类型。

this_thread的全局函数

C++11提供了命名空间this_thread来表示当前线程,该命名空间中有四个函数: get_id()sleep_for()``sleep_until()yield()

  1. get_id()
    thread:id get_id() noexcept;
    该函数用于获取线程ID,thread类也有同名的成员函数。
    在主程序中每一个子线程都有一个id,就跟我们的身份证号码一样。将这段话cout << this_thread::get_id() << endl;放在主函数中就是获取主函数的id,放在子线程中就是获得子线程的id。
    代码演示:

    #define _CRT_SECURE_NO_WARNINGS
    #include <iostream>
    #include<algorithm>
    #include<thread>
    #include<Windows.h>//Sleep()函数需要这个头文件
    using namespace std;
    void func(int bh, const string& str) {
    	//获取子线程的id
    	cout << "子线程:" << this_thread::get_id() << endl;
    	for (int i = 1;i <= 3;i++) {
    		cout << "第" << i << "次表白:亲爱的" << bh << "号," << str << endl;
    		Sleep(1000);
    	}
    }
    
    
    int main() {
    	//用普通函数创建线程
    	thread t1(func,3,"我是一只小小鸟");//创建线程对象
    	thread t2(func, 8, "我是一只傻傻鸟");
    
    	//主线程的id:
    	cout << this_thread::get_id() << endl;
    	t1.join();//回收线程t1的资源
    	t2.join();//回收线程t2的资源
    	
    }
    

    在这里插入图片描述
    或者说我们也可以这样:

    //用普通函数创建线程
    thread t1(func,3,"我是一只小小鸟");//创建线程对象
    thread t2(func, 8, "我是一只傻傻鸟");
    
    //主线程的id:
    cout << "主线程:" << this_thread::get_id() << endl;
    cout << "线程t1:" << t1.get_id() << endl;
    cout << "线程t2:" << t2.get_id() << endl;
    

    注意每次运行产生的id都是不同的。

  2. sleep_for()

    template<class Rep,class Period>
    void sleep_for(const chrono::duration<Rep,Period>& rel_time);
    

    该函数让线程休眠一段时间。
    也可以是

    Sleep(1000);//休眠一秒
    在Linux中就是sleep(1);//一秒
    
    #define _CRT_SECURE_NO_WARNINGS
    #include <iostream>
    #include<algorithm>
    #include<thread>
    #include<Windows.h>//Sleep()函数需要这个头文件
    using namespace std;
    void func(int bh, const string& str) {
    	//获取子线程的id
    	cout << "子线程:" << this_thread::get_id() << endl;
    	for (int i = 1;i <= 3;i++) {
    		cout << "第" << i << "次表白:亲爱的" << bh << "号," << str << endl;
    		//Sleep(1000);
    		this_thread::sleep_for(chrono::seconds(1));//休眠1s
    	}
    }
    
    
    int main() {
    	//用普通函数创建线程
    	thread t1(func,3,"我是一只小小鸟");//创建线程对象
    	thread t2(func, 8, "我是一只傻傻鸟");
    
    	//主线程的id:
    	cout << "主线程:" << this_thread::get_id() << endl;
    	cout << "线程t1:" << t1.get_id() << endl;
    	cout << "线程t2:" << t2.get_id() << endl;
    
    	
    	t1.join();//回收线程t1的资源
    	t2.join();//回收线程t2的资源
    	
    }
    
  3. sleep_until()

    template <class Clock,class Duration>
    vois sleep_until(const chrono::time_point<Clock,Duration>&abs_time);
    

    该函数让线程休眠至指定时间点。(可实现定时任务)
    这个用到不多,就不演示了

  4. yield()

    void yield() noexcept;
    

    该函数让线程主动让出自己已经抢到的CPU时间片。

  5. thread类其它的成员函数

    void swap(std::thread& other);//交换两个线程对象。
    static unsigned hardware_concurrency() noexcept;//返回硬件线程上下文的数量。
    
    #define _CRT_SECURE_NO_WARNINGS
    #include <iostream>
    #include<algorithm>
    #include<thread>
    #include<Windows.h>//Sleep()函数需要这个头文件
    using namespace std;
    void func(int bh, const string& str) {
    	//获取子线程的id
    	cout << "子线程:" << this_thread::get_id() << endl;
    	for (int i = 1;i <= 3;i++) {
    		cout << "第" << i << "次表白:亲爱的" << bh << "号," << str << endl;
    		//Sleep(1000);
    		this_thread::sleep_for(chrono::seconds(1));//休眠1s
    	}
    }
    
    
    int main() {
    	//用普通函数创建线程
    	thread t1(func,3,"我是一只小小鸟");//创建线程对象
    	thread t2(func, 8, "我是一只傻傻鸟");
    
    	//主线程的id:
    	cout << "主线程:" << this_thread::get_id() << endl;
    	cout << "线程t1:" << t1.get_id() << endl;
    	cout << "线程t2:" << t2.get_id() << endl;
    
    	t1.swap(t2);
    	cout << "线程t1:" << t1.get_id() << endl;
    	cout << "线程t2:" << t2.get_id() << endl;
    	
    	t1.join();//回收线程t1的资源
    	t2.join();//回收线程t2的资源
    	
    }
    

call_once函数

在多线程环境中,某些函数只能被调用一次,例如:初始化某个对象,而这个对象只能被初始化一次。
在线程的任务函数中,可以用std:.call_once()来保证某个函数只被调用一次。
头文件#include<mutex>

template<class callable,class... Args>
void call_once(std::once_flag&flag,Function&& fx,Args&&... args);

第一个参数是std::once_flag,用于标记fx是否已经执行过
第二个参数是只调用一次的函数名
第三个参数就是可变参数,用与传递给只调用一次的函数。

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include<algorithm>
#include<thread>
#include<Windows.h>//Sleep()函数需要这个头文件
using namespace std;
//线程中,打算只调用一次的函数
void once_func(const int bh, const string& str) {
	cout << "once_func() bh" << bh << ",str=" << str << endl;
}
once_flag onceflag;//once_flag全局变量,本质的取值为0和1的锁
void func(int bh, const string& str) {
	call_once(onceflag,once_func,0,"各位观众我要表白了");
	for (int i = 1;i <= 3;i++) {
		cout << "第" << i << "次表白:亲爱的" << bh << "号," << str << endl;
		this_thread::sleep_for(chrono::seconds(1));//休眠1s
	}
}


int main() {
	//用普通函数创建线程
	thread t1(func,3,"我是一只小小鸟");//创建线程对象
	thread t2(func, 8, "我是一只傻傻鸟");

	
	t1.join();//回收线程t1的资源
	t2.join();//回收线程t2的资源
	
}

在这里插入图片描述
还有一点需要注意,就是如果定义一个全局变量flag,然后判断全局变量是否执行,来判断是否执行一次,这样是不行的。

native_handle函数

C++11定义了线程标准,不同的平台和编译器在实现的时候,本质上都是对操作系统的线程库进行封装,会损失一部分功能。
为了弥补C++11线程库的不足,thread 类提供了native_handle()成员函数,用于获得与操作系
统相关的原生线程句柄,操作系统原生的线程库就可以用原生线程句柄操作线程。

#include<iostream>
#include<thread>
#include<pthread.h>
using namespace std;
void func(){
	for(int i=1;i<=10;i++){
		cout<<"ii="<<ii<<endl;
		this_thread::sleep_for(chrono::seconds(1));//休眠一秒
	}
}
int main(){
	thread tt(func);//创建线程
	this_thread::sleep_for(chrono::seconds(5));//休眠5秒
	pthread_t thid=tt.native_headle();//获取linux操作系统原生的线程句柄
	pthread_cancel(thid);//取消线程
	tt.join();//等待退出
}

线程安全

同一进程中的多个线程共享该进程中全部的系统资源。这样的话多个线程访问同一共享资源的时候就会产生冲突。
例如:我们运行之前创建线程的代码

这里有运行错乱的代码,这是因为cout是计算机中的共同资源,每个线程都用它向屏幕中输出数据。如果在同一时间点有多个线程使用cout,所以输出结果就是这样。
我们来看这一段代码也是:

如果不用线程的话,应该输出2000000.
所以说:
多个线程访问同一共享资源的时候会产生冲突。
顺序性、可见性、原子性。

  • 顺序性
    程序按照代码的先后顺序执行。
    CPU为了提高程序整体的执行效率,可能会对代码进行优化,按更高效的顺序执行。
    CPU虽然不保证完全按照代码的顺序执行,但它会保证最终的结果和按代码顺序执行时的结果一致。

  • 可见性
    线程操作共享变量时,会将该变量从内存加载到CPU缓存中,修改该变量后,CPU会立即更新缓存,但不一定会立即将它写回内存。这时候,如果其它线程访问该变量,从内存中读到的是旧数据,而非第一个线程操作后的数据。
    当多个线程并发访问共享变量时,一个线程对共享变量的修改,其它线程能够立即看到。

  • 原子性
    CPU执行指令:读取指令、读取内存、执行指令、写回内存。
    i++ 1)从内存中读取i的值; 2)把i+1; 3)把结果写回内存。
    一个操作(有可能包含有多个步骤)要么全部执行(生效),要么全部都不执行

如何保证线程安全

  1. volatile关键字
    volatile关键字可以保证内存变量的可见性,并且禁止代码优化(重排序) 。但是他解决不了,线程安全,要解决线程安全只能用下面两个方法

    #define _CRT_SECURE_NO_WARNINGS
    #include <iostream>
    #include <vector>
    #include <cmath>
    #include <fstream>//ifstream 类需要包含的头文件
    #include<string>
    #include<algorithm>
    #include<cassert>
    #include<thread>
    #include<initializer_list>
    #include<chrono>
    #include<iomanip>
    #include<Windows.h>//Sleep()函数需要这个头文件
    using namespace std;
    volatile int aa = 0;//定义全局变量
    void func() {
    	for (int i = 1;i <= 1000000;i++) {
    		aa++;
    	}
    }
    int main() {
    	//func();
    	//func();
    	//用普通函数创建线程
    	thread t1(func);//创建线程t1,把全局变量aa加1000000次
    	thread t2(func);//创建线程t2,把全局变量aa加1000000次 
    	t1.join();//回收线程t1的资源
    	t2.join();//回收线程t2的资源
    	cout << "aa=" << aa << endl;
    }
    

    在这里插入图片描述

  2. 原子操作(原子类型)

  3. 线程同步(锁)

线程同步

其实线程同步就是:多个线程协同工作,协商如何使用共享资源。C++11线程同步包含这三个方面:

  • 互斥锁(互斥量)
  • 条件变量
  • 生产/消费者模型

互斥锁

  • 加锁和解锁,确保同一时间只有一个线程访问共享资源。
  • 访问共享资源之前加锁,访问完成后释放锁。
  • 如果某线程持有锁,其它的线程形成等待队列。

C++提供了四种互斥锁:

  • mutex:互斥锁
  • time_mutex:待超时机制的互斥锁
  • recursive_mutex:递归互斥锁
  • recursive_timed_mutex:待超时机制的递归互斥锁

包含头文件#include<mutex>

mutex 类

  1. 加锁lock()
    互斥锁有锁定和未锁定两种状态。
    如果互斥锁是未锁定状态,调用lock()成员函数的线程会得到互斥锁的所有权,并将其上锁。
    如果互斥锁是锁定状态,调用lock()成员函数的线程就会阻塞等待,直到互斥锁变成未锁定状态。
  2. 解锁unlock()
    只有持有锁的线程才能解锁。
    例如:
    对于这个代码:
    #define _CRT_SECURE_NO_WARNINGS
    #include <iostream>
    #include <vector>
    #include <cmath>
    #include <fstream>//ifstream 类需要包含的头文件
    #include<string>
    #include<algorithm>
    #include<cassert>
    #include<thread>
    #include<initializer_list>
    #include<chrono>
    #include<iomanip>
    #include<Windows.h>//Sleep()函数需要这个头文件
    using namespace std;
    void func(int bh, const string& str) {
    	for (int i = 1;i <= 10;i++) {
    		cout << "第" << i << "次表白:亲爱的" << bh << "号," << str << endl;
    		this_thread::sleep_for(chrono::seconds(1));//休眠一秒
    	}
    }
    int main() {
    	//用普通函数创建线程
    	thread t1(func, 1,"我是一只小小鸟");
    	thread t2(func, 2, "我是一只小小鸟");
    	thread t3(func, 3, "我是一只小小鸟");
    	thread t4(func, 4, "我是一只小小鸟");
    	thread t5(func, 5, "我是一只小小鸟");
    	
    	t1.join();//回收线程t1的资源
    	t2.join();//回收线程t2的资源
    	t3.join();//回收线程t3的资源
    	t4.join();//回收线程t4的资源
    	t5.join();//回收线程t5的资源
    }
    
    在这里插入图片描述
    #define _CRT_SECURE_NO_WARNINGS
    #include <iostream>
    #include <vector>
    #include <cmath>
    #include <fstream>//ifstream 类需要包含的头文件
    #include<string>
    #include<algorithm>
    #include<cassert>
    #include<thread>
    #include<initializer_list>
    #include<chrono>
    #include<mutex>
    #include<iomanip>
    #include<Windows.h>//Sleep()函数需要这个头文件
    using namespace std;
    
    mutex mtx;//创建互斥锁,保护共享资源cout对象
    
    void func(int bh, const string& str) {
    	for (int i = 1;i <= 10;i++) {
    		mtx.lock();//每次申请cout资源的时候都加锁
    		cout << "第" << i << "次表白:亲爱的" << bh << "号," << str << endl;
    		mtx.unlock();//用完了就解锁
    		this_thread::sleep_for(chrono::seconds(1));//休眠一秒
    	}
    }
    int main() {
    	//用普通函数创建线程
    	thread t1(func, 1,"我是一只小小鸟");
    	thread t2(func, 2, "我是一只小小鸟");
    	thread t3(func, 3, "我是一只小小鸟");
    	thread t4(func, 4, "我是一只小小鸟");
    	thread t5(func, 5, "我是一只小小鸟");
    	
    	t1.join();//回收线程t1的资源
    	t2.join();//回收线程t2的资源
    	t3.join();//回收线程t3的资源
    	t4.join();//回收线程t4的资源
    	t5.join();//回收线程t5的资源
    }
    
    然后好了:
    在这里插入图片描述
    然后对于这个也可以了:
    在这里插入图片描述
    然后对于这个代码我们可以很清楚的看到线程堵塞,线程等待
    #define _CRT_SECURE_NO_WARNINGS
    #include <iostream>
    #include <vector>
    #include <cmath>
    #include <fstream>//ifstream 类需要包含的头文件
    #include<string>
    #include<algorithm>
    #include<cassert>
    #include<thread>
    #include<initializer_list>
    #include<chrono>
    #include<iomanip>
    #include<mutex>
    #include<Windows.h>//Sleep()函数需要这个头文件
    using namespace std;
    int aa = 0;//定义全局变量
    mutex mtx;//创建互斥锁,保护共享资源aa变量
    void func() {
    	for (int i = 1;i <= 1000000;i++) {
    		cout << "线程" << this_thread::get_id() << "申请加锁...\n";
    		mtx.lock();//访问共享资源的时候加锁
    		cout << "线程" << this_thread::get_id() << "加锁成功\n";
    		aa++;
    		this_thread::sleep_for(chrono::seconds(5));//休眠5秒
    		mtx.unlock();//解锁
    		this_thread::sleep_for(chrono::seconds(1));//休眠1秒
    	}
    }
    int main() {
    	//func();
    	//func();
    	//用普通函数创建线程
    	thread t1(func);//创建线程t1,把全局变量aa加1000000次
    	thread t2(func);//创建线程t2,把全局变量aa加1000000次 
    	t1.join();//回收线程t1的资源
    	t2.join();//回收线程t2的资源
    	cout << "aa=" << aa << endl;
    }
    
  3. 尝试加锁trylock()
    如果互斥锁是未锁定状态,则加锁成功,函数返回true。
    如果互斥锁是锁定状态,则加锁失败,函数立即返回false。(线程不会阻塞等待)
    这个用途就是这样的:
    在这里插入图片描述
    对于这个公共卫生间,我们尝试进一个,有人的话我们不用等待,换下一个。

timed_mutex类

增加了两个成员函数:

bool try_lock_for(时间长度);
bool try_lock_until(时间点);
timed_mutex tmx;
tmx.try_lock_for(chrono::seconds(10));//10秒

在这里插入图片描述
这个意义就是如果憋不住了,换地方,不能一直等着。

recursive_mutex类

递归互斥锁允许同一线程多次获得互斥锁,可以解决同一线程多次加锁造成的死锁问题。

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <vector>
#include <cmath>
#include <fstream>//ifstream 类需要包含的头文件
#include<string>
#include<algorithm>
#include<cassert>
#include<thread>
#include<initializer_list>
#include<chrono>
#include<iomanip>
#include<mutex>
#include<Windows.h>//Sleep()函数需要这个头文件
using namespace std;
class AA {
	mutex m_mutex;
public:
	void func1() {
		m_mutex.lock();
		cout << "调用了func1()\n";
		m_mutex.unlock();
	}
	void func2() {
		m_mutex.lock();
		cout << "调用了func2()\n";
		func1();//调用1
		m_mutex.unlock();
	}
};
int main() {
	
	AA aa;
	//aa.func1();
	aa.func2();
}

例如这个代码,我们在func2中调用func1会产生死锁。
然后我们将互斥类型改为recursive_mutex m_mutex;就可以了

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <vector>
#include <cmath>
#include <fstream>//ifstream 类需要包含的头文件
#include<string>
#include<algorithm>
#include<cassert>
#include<thread>
#include<initializer_list>
#include<chrono>
#include<iomanip>
#include<mutex>
#include<Windows.h>//Sleep()函数需要这个头文件
using namespace std;
class AA {
	recursive_mutex m_mutex;
public:
	void func1() {
		m_mutex.lock();
		cout << "调用了func1()\n";
		m_mutex.unlock();
	}
	void func2() {
		m_mutex.lock();
		cout << "调用了func2()\n";
		func1();//调用1
		m_mutex.unlock();
	}
};
int main() {
	
	AA aa;
	//aa.func1();
	aa.func2();
}

lock_guard类

lock_guard是模板类,可以简化互斥锁的使用,也更安全。
lock_guard的定义如下:

template<class Mutex>
class lock_guarde{
explicit lock_guard(Mutex& mtx);
}

lock_guard在构造函数中加锁,在析构函数中解锁。
lock_guard采用了RAII 思想(在类构造函数中分配资源,在析构函数中释放资源,保证资源在离开作用域时自动释放)。
这个就跟智能指针一样,不用加锁也不用解锁了

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <vector>
#include <cmath>
#include <fstream>//ifstream 类需要包含的头文件
#include<string>
#include<algorithm>
#include<cassert>
#include<thread>
#include<initializer_list>
#include<chrono>
#include<iomanip>
#include<mutex>
#include<Windows.h>//Sleep()函数需要这个头文件
using namespace std;
int aa = 0;//定义全局变量
mutex mtx;//创建互斥锁,保护共享资源aa变量
void func() {
	for (int i = 1;i <= 1000000;i++) {
		//mtx.lock();
		lock_guard<mutex>mlock(mtx);
		aa++;
		//mtx.unlock();
	}
}
int main() {
	//func();
	//func();
	//用普通函数创建线程
	thread t1(func);//创建线程t1,把全局变量aa加1000000次
	thread t2(func);//创建线程t2,把全局变量aa加1000000次 
	t1.join();//回收线程t1的资源
	t2.join();//回收线程t2的资源
	cout << "aa=" << aa << endl;
}

生产者消费者模型

这个比较重要所以有新开了一个文章写这个。

原子类型atomic

我们上面说的解决相加不是2000000的问题,我们
可以用线程同比来解决,也可以用原子类型来解决

C++11提供了atomic<T>模板类(结构体),用于支持原子类型,模板参数可以是bool、char、int、long、long long、指针类型(不支持浮点类型和自定义数据类型)
原子操作由CPU指令提供支持,它的性能比锁和消息传递更高,并且,不需要程序员处理加锁和释放锁的问题,支持修改、读取、交换、比较并交换等操作。
头文件:#include <atomic>

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <vector>
#include <cmath>
#include <fstream>//ifstream 类需要包含的头文件
#include<string>
#include<algorithm>
#include<cassert>
#include<thread>
#include<initializer_list>
#include<chrono>
#include<iomanip>
#include<Windows.h>//Sleep()函数需要这个头文件
#include<atomic>
using namespace std;
atomic<int> aa = 0;//定义全局变量
void func() {
	for (int i = 1;i <= 1000000;i++) {
		aa++;
	}
}
int main() {
	//func();
	//func();
	//用普通函数创建线程
	thread t1(func);//创建线程t1,把全局变量aa加1000000次
	thread t2(func);//创建线程t2,把全局变量aa加1000000次 
	t1.join();//回收线程t1的资源
	t2.join();//回收线程t2的资源
	cout << "aa=" << aa << endl;
}

这个和线程同比一样,但是这个不用加锁。
对于互斥锁可以解决线程同步的问题,就是代价比较高,就像交通路口的红绿灯一样,如果一个线程持有锁,其他线程就会堵塞等待,而原子类型就像高架桥一样,每个反向都可以通行,效率更高。原子操作由CPU指今提供支持,是轻量级的锁,不是完全没有锁。
构造函数:

atomic() noexcept = default;// 默认构造函数。
atomic(T val) noexcept;//转换函数。
atomic(const atomic&) = delete;//禁用拷贝构造函数。

赋值函数:

atomic& operator=(const atomic&) = delete;//禁用

常用函数:

void store(T val) noexcept;//把 val的值存入原子变量。
T load() noexcept;//读取原子变量中的值。
T fetch_add(T val) noexcept;//把原子变量的值与val相加,返回原值。
T fetch_sub(T val) noexcept;//把原子变量的值减val,返回原值。
T exchange(T val) noexcept;/l把val的值存入原子变量,返回原值。
T compare_exchange_strong(T &expect,T val) noexcept; //比较原子变量的值和预期值expect,如果当两个值相等,把val存储到原子变量中,函数返回true;如果当两个值不相等,用原子变量的值更新预期值,函数返回false.CAS指令

bool is_lock_free(); //查询某原子类型的操作是直接用CPU指令(返回true),还是编译器内部的锁(返回false)。

代码:

#include<algorithm>
#include<cassert>
#include<thread>
#include<initializer_list>
#include<chrono>
#include<iomanip>
#include<Windows.h>//Sleep()函数需要这个头文件
#include<atomic>
#include<iostream>
using namespace std;


int main() {
	atomic<int> a = 3;//atomic(T val) noexcept;//转化函数
	cout << "a=" << a.load() << endl;//读取原子变量中a的值
	a.store(8);//把8储存到原子变量中
	cout << "a=" << a.load() << endl;//读取
	int old;//用于存放原址
	old = a.fetch_add(5);
	cout << "old=" << old << ",a=" << a.load() << endl;//old=9,a=13
	old = a.fetch_sub(2);//原子变量a的值-2
	cout << "old=" << old << ",a=" << a.load() << endl;//old=13,a=11
}
#include<algorithm>
#include<cassert>
#include<thread>
#include<initializer_list>
#include<chrono>
#include<iomanip>
#include<Windows.h>//Sleep()函数需要这个头文件
#include<atomic>
#include<iostream>
using namespace std;


int main() {
	atomic<int> ii = 3;//原子变量
	int expect = 4;//期待值
	int val = 5;//打算存入原子变量的值
	//比较原子变量的值和预期值expect,
	//如果当两个值相等,把val存储到原子变量中;
	//如果当两个值不相等,用原子变量的值更新预期值。
	//执行存储操作时返回true,否则返回false。
	bool bret = ii.compare_exchange_strong(expect, val);
	cout << "bret=" << bret << endl;
	cout << "ii=" << ii << endl;
	cout << "expect=" << expect << endl;

}

注意:

  • atomic<T>模板类重载了整数操作的各种运算符。
  • atomic<T>模板类的模板参数支持指针,但不表示它所指向的对象是原子类型。
  • 原子整型可以用作计数器,布尔型可以用作开关。
  • CAS指令是实现无锁队列基础。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值