创建线程
在C++11之前,C++没有对线程提供语言级别的支持,各种操作系统和编译器实现线程的方法不一样。
C++11增加了线程以及线程相关的类,统一编程风格、简单易用、跨平台。
创建线程
头文件#include<thread>
线程类std::thread
构造函数:
-
thread() noexcept
默认构造函数,构造一个线程对象,不执行任何任务(不会启动子线程) -
常用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(); }
-
thread(const thread& ) = delete;
删除拷贝构造函数,不允许线程对象之间的拷贝。 -
thread(thread&& other ) noexcept;
移动构造函数,将线程other的资源所有权转移给新创建的线程对象。
赋值函数:
thread& operator= (thread&& other) noexcept;
thread& operator= (const other&) = delete;
线程中的资源不能被复制,如果other是右值,会进行资源所有权的转移,如果other是左值,禁止拷贝。
注意:
- 先创建的子线程不一定跑得最快(程序运行的速度有很大的偶然性)。
- 线程的任务函数返回后,子线程将终止。
- 如果主程序(主线程)退出(不论是正常退出还是意外终止),全部的子线程将强行被终止。
线程资源回收
虽然同一个进程的多个线程共享进程的栈空间,但是,每个子线程在这个栈中拥有自己私有的栈空间。所以,线程结束时需要回收资源。
回收子线程的资源有两种方法:
- 在主程序中,调用
join()
成员函数等到子线程推出,回收他的资源,如果子线程已推出,join()函数立即返回,否则会产生堵塞,知道子线程推出。 - 在子线程中,调用
detach()
成员函数分离子线程,子线程退出时,系统自动回收资源。joinable()
成员函数可以判断子线程的分离状态,函数返回布尔类型。
用joinable()
成员函数可以判断子线程的分离状态,函数返回布尔类型。
this_thread的全局函数
C++11提供了命名空间this_thread
来表示当前线程,该命名空间中有四个函数: get_id()
、sleep_for()``sleep_until()
、yield()
。
-
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都是不同的。
-
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的资源 }
-
sleep_until()
template <class Clock,class Duration> vois sleep_until(const chrono::time_point<Clock,Duration>&abs_time);
该函数让线程休眠至指定时间点。(可实现定时任务)
这个用到不多,就不演示了 -
yield()
void yield() noexcept;
该函数让线程主动让出自己已经抢到的CPU时间片。
-
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)把结果写回内存。
一个操作(有可能包含有多个步骤)要么全部执行(生效),要么全部都不执行
如何保证线程安全
-
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; }
-
原子操作(原子类型)
-
线程同步(锁)
线程同步
其实线程同步就是:多个线程协同工作,协商如何使用共享资源。C++11线程同步包含这三个方面:
- 互斥锁(互斥量)
- 条件变量
- 生产/消费者模型
互斥锁
- 加锁和解锁,确保同一时间只有一个线程访问共享资源。
- 访问共享资源之前加锁,访问完成后释放锁。
- 如果某线程持有锁,其它的线程形成等待队列。
C++提供了四种互斥锁:
- mutex:互斥锁
- time_mutex:待超时机制的互斥锁
- recursive_mutex:递归互斥锁
- recursive_timed_mutex:待超时机制的递归互斥锁
包含头文件#include<mutex>
mutex 类
- 加锁
lock()
互斥锁有锁定和未锁定两种状态。
如果互斥锁是未锁定状态,调用lock()
成员函数的线程会得到互斥锁的所有权,并将其上锁。
如果互斥锁是锁定状态,调用lock()
成员函数的线程就会阻塞等待,直到互斥锁变成未锁定状态。 - 解锁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; }
- 尝试加锁
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指令是实现无锁队列基础。