C++11多线程(七) C++11对多线程的一些补充

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,布尔型原子变量也能实现让某个函数只执行一次。但既然有封装好的我们就不需要在自己多写代码了。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值