C++之线程库
前言
C++的线程库,不是只有线程,有好几个库,包括了创建线程,锁,条件变量,原子操作等待
thread类
在C++11之前,涉及到多线程问题,都是和平台相关的,比如windows和linux下各有自己的接口,这使得代码的可移植性比较差。C++11中最重要的特性就是对线程进行支持了,使得C++在并行编程时不需要依赖第三方库,而且在原子操作中还引入了原子类的概念。要使用标准库中的线程,必须包含< thread >头文件
构造函数
线程函数的参数
关于线程函数的参数是以值拷贝的方式拷贝到线程栈空间中的,因此:即使线程参数为引用类型,在线程中修改后也不能修改外部实参,因为其实际引用的是线程栈中的拷贝,而不是外部实参。
mutex类
在多线程的情况下,有可能会出现数据不一致的问题!
#include<thread>
#include<iostream>
#include<vector>
using namespace std;
int g_val = 0;
void func1(int n)
{
for (size_t i = 0; i < n; i++)
{
g_val++;
}
}
int main()
{
thread t1(func1,100000);
thread t2(func1,100000);
t1.join();
t2.join();
cout << g_val << endl;
}
、
==为了解决这个问题!所以我们可以通过加锁来解决!C++也给我们提供了mutex库用==
mutex类的构造函数
atomic类
上面的数据不一致问题除了使用加锁的,还能使用原子操作!
**数据不一致的本质原因是因为++这个操作本身不是原子的!——那么我们只要让这个操作本身就变成原子的不就好了? **
==原子操作一般都是操作系统提供的!——CAB(Compare and Swap)==
==回到最开始我们如果不加锁的保持数据一致性呢?==
#include<thread>
#include<iostream>
#include<vector>
#include<atomic>
using namespace std;
atomic<int> g_val = 0;//原子操作!
void func1(int n)
{
for (size_t i = 0; i < n; i++)
{
g_val++;
}
}
int main()
{
thread t1(func1,1000);
thread t2(func1,1000);
t1.join();
t2.join();
cout << g_val << endl;
}
==这样就成功了!——这个比里面加锁快!比里面加锁慢!——因为CAS会导致有些++操作会被放弃!==
但是上面有一个问题,那就是在多线程中,我们不推荐使用全局的对象!
所以我们可以用一个更好的方案
#include<thread>
#include<iostream>
#include<vector>
#include<atomic>
int main()
{
atomic<int> aval = 0;
auto func = [&aval](int n)
{
for (size_t i = 0; i <n; i++)
{
++aval;
}
};
thread t1(func,3000);
thread t2(func,1000);
t1.join();
t2.join();
cout << aval << endl;
}
==这就是lambda表达式的好处!可以捕抓局部的变量!==
这样也可以看出,在栈上的变量不一定是线程安全的!因为有lambda表达式可以用来捕抓栈上的变量!——==只要能被多个线程访问到都要考虑是不是线程安全的!==
lock_guard类
int main()
{
atomic<int> aval = 0;
mutex mtx;
auto func = [&](int n)
{
for (size_t i = 0; i <n; i++)
{
aval++;
mtx.lock();
int* a = new int();//如果这个new抛异常了该怎么办?
cout << this_thread::get_id() << ":" << aval << endl;
mtx.unlock();
}
};
thread t1(func,3000);
thread t2(func,1000);
t1.join();
t2.join();
cout << aval << endl;
}
这样的情况会不会造成一个线程抛异常后退出,但是锁却没有释放?
==我们可以使用RAII的思想去解决这个问题!构造的时候加锁!析构的时候解锁!让其出了作用域后自动的调用析构函数解锁即可!==
==库里面就已经帮我们封装好了!lock_gurad类==
我们可以看到他主要就是给我们提供了一个构造和析构!
这个锁是一个模板,只要是锁类型的都可以!
int main()
{
atomic<int> aval = 0;
mutex mtx;
auto func = [&](int n)
{
for (size_t i = 0; i <n; i++)
{
{
lock_guard<mutex> lock(mtx);//这样就可以保证出了作用域锁自动的释放!
int* a = new int();
cout << this_thread::get_id() << ":" << aval << endl;
//我们还可以通过作用域来控制锁的范围!
}
aval++;
}
};
thread t1(func,3000);
thread t2(func,1000);
t1.join();
t2.join();
cout << aval << endl;
}
unique_lock
unique_lock是比lock_gurad==更加灵活==的一个锁管理对象!
==unique_lock具有lock_guard的所有特性!——例如构造的时候获取锁!析构的时候释放锁!同时还提供了更多的可可选操作!==
构造函数
condition_variable类
C++给我们提供的关于条件变量的类!
构造函数
唤醒操作函数
notify_one
唤醒该条件变量下的一个线程!(在linux下面相当于pthread_cond_signal函数)
notify_all
唤醒该条件变量下面的所有线程!(相当linux下面的pthread_cond_broadcast)
条件变量的应用——支持两个线程交替打印!一个打印奇数,一个打印偶数!
错误写法!
#include<thread>
#include<iostream>
#include<vector>
int main()
{
int val = 0;//这是共享资源!
thread Odd([&] {
while (val <100)
{
if (val % 2 == 1)
{
cout << this_thread::get_id() << ":" << val << endl;
++val;
}
}
});
thread Even([&] {
while (val<100)
{
if (val % 2 == 0)
{
cout << this_thread::get_id() << ":" << val++ << endl;
++val;
}
}
});
Odd.join();
Even.join();
return 0;
}