文章目录
C++11多线程(七) C++11对多线程的一些补充
thread_local
thread_local是一个新引入的关键字,主要用于声明一个变量的作用域。
thread_local int x = 0;
//这样我们就声明了一个int变量的作用域
thread_local代表的作用域是从一个线程的开始到一个线程的结束。利用thread_local我们可以实现让每一个线程都拥有自己的实例对象。不止全局变量,我们也可以在声明thread_local局部变量和类对象,同时也可以使用extern和static来修饰这个变量。
看一下三种情况的区别
全局变量
thread_local全局变量在每个新创建线程内都会被复制一次,也就是说t1,t2两个线程内都有一个拷贝出来的x变量,两者互不影响。
#include<iostream>
#include <thread>
#include <mutex> //输出安全性
using namespace std;
mutex mtx;
thread_local int x = 0; //定义一个全局thread_local变量x
void f() {
mtx.lock();
x++;
cout << x << endl;
mtx.unlock();
}
int main() {
thread t1(f);
thread t2(f);
t1.join();
t2.join();
}
运行结果:
1
1
上述代码里就有一个t1里的局部变量x,t2里的局部变量x,两者互不影响。因此最终输出都是1。如果不理解我们可以看看下面的改进版。
#include<iostream>
#include <thread>
#include <mutex> //输出安全性
using namespace std;
mutex mtx;
thread_local int x = 0; //定义一个全局thread_local变量x
void f1() {
mtx.lock();
x = 1111;
cout << x << endl;
mtx.unlock();
}
void f2() {
mtx.lock();
x = 2222;
cout << x << endl;
mtx.unlock();
}
int main() {
thread t1(f1);
thread t2(f2);
t1.join();
t2.join();
cout << x << endl;
}
输出结果:
1111
2222
0
对比之前的代码,这次我们就能很清楚的看出效果来了,f1里的x、f2里的x和全局里的变量x三者是互不影响的。再次强调,在每一个thread线程里都会复制并初始化所定义的thread_local变量交给线程使用。
局部变量
#include<iostream>
#include <thread>
#include <mutex> //输出安全性
using namespace std;
mutex mtx;
void f2() {
for(int i =0;i<10;++i){
//定义一个局部thread_local变量x
thread_local int x = 0;
cout<<x++<<" ";
}
//cout<<x<<endl;
}
int main() {
thread t2(f2);
t2.join();
}
thread_local局部对象和全局对象是相似的,只是局部对象的粒度更小,更方便我们使用。
虽然上述代码我们把x定义在了for循环内,但x确实在第一次定义并初始化后会一直存在到这段作用域(本段代码中指for循环)。
上述代码运行结果:0 1 2 3 4 5 6 7 8 9
**thread_local局部变量**只会初始化一次然后一直存在到相应的作用域结束。所以第十二行的代码是错误的,因为超出了作用域。
类对象
类对象和不同变量是一样的效果,只是看你定义为局部变量还是全局变量。
#include<iostream>
#include <thread>
#include<string>
#include <mutex> //输出安全性
using namespace std;
mutex mtx;
class T
{
public:
T() { m_x = 100; };
~T() = default;
operator int() { return m_x; }
T(const T& other) {cout << "拷贝构造函数被调用" << endl;}; //拷贝构造函数
int m_x;
};
thread_local T x;
void f1() {
x.m_x = 66;
mtx.lock();
cout << x << endl;
mtx,unlock();
}
void f2() {
x.m_x = 99;
mtx.lock();
cout << x << endl;
mtx,unlock();
}
int main() {
thread t1(f1);
thread t2(f2);
t1.join();
t2.join();
}
输出结果:
66 99
std::this_thread::yield();
在这里先学习一下线程的几个状态
新建状态(新创建的线程对象)
就绪状态(获得所有所需资源等待cpu来运行)
运行状态(正在被CPU执行)
阻塞状态(因为某种原因放弃CPU的使用权,进入准备状态)
还记得我们之前学的wait、sleep_for、wait_for等等一系列方法吗?这些都是让线程进入阻塞状态。
现在再介绍一个方法,std::this_thread::yield()。
yield()会让执行该方法的线程让出时间片,直到满足条件。注意这里的条件是我们外部定义的,因为yield()是不接受任何参数的。
yield()会在可以运行之后立马获得时间片。
#include<iostream>
#include <thread>
using namespace std;
int i = 0;
void f1() {
while (i<=100)
{
i++;
cout << i << endl;
}
}
void f2() {
cout << "begin" << endl;
while (i <= 100) std::this_thread::yield();//当i小于100的时候就阻塞在原地
//注意这里使用while循环
//因为先创建f1再创建f2,所以begin会比前几个数字输出慢一些
//但end永远是在100之后输出,就是因为yield阻塞住了f2的运行
cout << "end" << endl;
}
int main() {
thread t1(f1);
thread t2(f2);
t1.join();
t2.join();
}
yield()和condition_variable::wait()是类似的,但是使用起来会更加简单,分情况考虑使用。
std::once_flag和std::call_once
在多线程程序中,有时我们会想只使用某些代码一次,这种时候我们就可以使用std::once_flag和std::call_once。
std::once_flag是一个结构体,先来看看源码
#include<mutex> //包含的头文件
struct once_flag
{ // opaque data structure for call_once()
_CONST_FUN once_flag() _NOEXCEPT
: _Opaque(0)
{ // default construct
}
once_flag(const once_flag&) = delete;
once_flag& operator=(const once_flag&) = delete;
void *_Opaque;
};
once_flag禁用了拷贝构造函数和赋值运算符,注意它是个结构体,有关结构体和类的区别自己可以思考一下。
了解了once_flag以后,再来看看call_once();
call_once()有多个参数(最少两个),第一个要求传入一个once_flag,第二个则是传入一个可调用对象(函数,成员方法,匿名表达式),之后根据你传入的可调用对象传入所需要的参数。
#include<mutex> //头文件
dosomething();
/*
...
*/
std::once_flag Flag;
std::call_once(Flag, dosomething);
std::call_once(Flag, dosomething);//第二次就会失败
上述代码中我们只想执行一次dosomething()函数的话就可以像上述这样使用。
下面看看实际代码
#include<iostream>
#include <thread>
#include<mutex>
using namespace std;
std::once_flag Flag;//全局标志判断
void f3() {
call_once(Flag,[]{
cout << "f3" << endl;
});
}
int main() {
thread t1(f3);
thread t2(f3);
thread t3(f3);
thread t4(f3);
thread t5(f3);
t1.join();
t2.join();
t3.join();
t4.join();
t5.join();
}
输出结果:
f3
当其中某个线程调用过f3之后,剩下的线程想再次尝试执行f3之后就会失败,所以只有一个f3输出。
#include<iostream>
#include <thread>
#include<mutex>
using namespace std;
std::once_flag Flag;//全局标志判断
void f3() {
call_once(Flag,[]{
cout << "f3" << endl;
});
}
int main() {
thread t1(f3);
thread t2(f3);
thread t3(f3);
thread t4(f3);
thread t5(f3);
t1.join();
t2.join();
t3.join();
t4.join();
t5.join();
}
输出结果:
f3
当其中某个线程调用过f3之后,剩下的线程想再次尝试执行f3之后就会失败,所以只有一个f3输出。
其实利用之前所所讲过的atomic_bool,布尔型原子变量也能实现让某个函数只执行一次。但既然有封装好的我们就不需要在自己多写代码了。