C11 多线程初学1

1、线程的创建和使用

引入线程库<thread>,其提供了thread类,创建一个线程即实例化一个该类的对象,以此进行线程的创建和使用。

#include<thread>
using namespce std;

1.1 创建线程

一个线程可以用一个 thread 类的对象来表示,thread类中重载了构造函数和移动拷贝构造

#include<iostream>
#include<thread>

using namespace std;

void funa()
{
	cout << "thread_funa()" << endl;
}
int main()
{
	//创建线程——构造函数
	thread a(funa);
	cout << a.get_id() << endl;
	//移动拷贝构造
	thread b(std::move(a));
	cout << b.get_id() << endl;

	b.join();
	//移动拷贝构造进行资源的交换,所有要注释掉
	//a.join();
	cout << "main end" << endl;

}

注意: thread类无拷贝构造函数、无赋值运算符,即不能直接用一个事先定义好的thread对象拷贝构造另一个thread对象,也不能不能进行赋值操作。但可以将临时的thread对象赋值给另一个thread即移动拷贝构造,也可以移动赋值。

1.2 线程的使用

thread 提供的函数: 线程支持库
thread类常用的成员函数:

成员函数含义
get_id()获取当前thread对象的线程id
joinable()判断当前线程是否活跃(是:true;否:false)即是否支持调用 join()
join()阻塞当前thread 对象所在的线程,直至thread 对象表示的线程执行完毕
detach()将当前线程从调用该函数的线程中分离出去,他们彼此独立运行
swap()交换两个线程的状态

注意:每个thread 对象在调用析构销毁前,要么调用 join() 函数令主线程等待子线程执行完毕,要么调用detach() 函数将子线程和主线程分离,二者必选一,否则存在以下问题:

  • 线程占用的资源无法全部释放,造成内存泄露
  • 当主线程执行完毕而子线程未完时,程序执行引发异常。

在this_thread 命名空间的部分函数:

函数含义
get_id()获得当前线程的 ID
yield()阻塞当前线程让出cpu,直至条件成熟
sleep_for()阻塞当前线程指定的时间
sleep_until()阻塞当前线程,直至某个时间点为止

例:

//休眠1000毫秒,1s
this_thread::sleep_for(chrono::milliseconds(1000));

2.实现线程同步

2.1互斥锁

互斥锁用 mutex 类(位于std命名空间中)的对象表示,定义在头文件<mutex>头文件中。
成员函数:lock()——加锁; unlock()——解锁
例:对临界资源变量n的访问

#include<mutex>
using namespcase std;
mutex m; //实例化mutex对象m
void Show_n()
{
	m.lock();
	cout << n << endl;
	m.unlock();
}

实现严格基于作用域的互斥体所有权包装器:lock_fuard
原理:创建 lock_guard 对象时,它试图接收给定互斥的所有权。控制离开创建 lock_guard 对象的作用域时,销毁 lock_guard 并释放互斥。即,创建即加锁,作用域结束自动解锁。

#include<mutex>
using namespcase std;
mutex m; //实例化mutex对象m
void Show_n()
{
	lock_guard<mutex> g1(m);
	cout << n << endl;
}
//作用域结束,析构g1,m解锁

lock_gurad传入两个参数,第二个为adopt_lock标识时,表示该互斥量之前必须已经lock,才能使用此参数,故需要提前手动锁定。

#include<mutex>
using namespcase std;
mutex m; //实例化mutex对象m
void Show_n()
{
	m.lock(); //手动锁定
	lock_guard<mutex> g1(m, adopt_lock);
	cout << n << endl;
}
//作用域结束,析构g1,m解锁

实现可移动的互斥体所有权包装器:unique_lock
与lock_guard类似,用法更丰富。

成员函数含义
lock()锁定
try_lock()尝试锁定,若不能,先去执行其他代码并返回false;可以,进行加锁并返回true
unlock()解锁

unique_lock与lock_guard

unique_locklock_guard
手动lock、unlock支持不支持
参数支持 adopt_lock/try_to_lock/defer_lock支持 adopt_lock

try_to_lock: 尝试用mutx的lock()去锁定这个mutex,但如果没有锁定成功,会立即返回,不会阻塞在那里
defer_lock: 这个参数表示暂时先不lock,之后手动去lock,但是使用之前也是不允许去lock。

2.2 条件变量

C11提供的两种表示条件变量的类:
都定义在<condition_variable>头文件中,常与互斥锁搭配使用,避免线程间抢夺资源。

  • condition_variable
    该类表示的条件变量只能和unique_lock类表示的互斥锁搭配使用;
  • condition_variable_any
    该类表示的条件变量可以与任意类型的互斥锁搭配使用(如:递归互斥锁、定时互斥锁等)

条件变量常用的成员函数

成员函数功能
wait()阻塞当前线程,等待条件成立
wait_for()阻塞当前线程的过程中,该函数会自动调用unlock()解锁,令其他线程使用公共资源。当条件成立或超过指定的等待时间,该函数自动调用lock()加锁,同时令线程继续执行
wait_until()与wait_for() 类似,区别是其可以设定一个具体的时间点,当条件成立或等待时间超过了指定的时间点,函数自动加锁,线程执行
notify_one()唤醒一个等待的线程
notify_all()唤醒所有等待线程

关于wait() :
调用wait()1先获得mutex,2线程被阻塞,当wait陷入休眠是会自动释放mutex。直到另外某个线程调用 notify_one或notify_all唤醒了当前线程。当线程被唤醒时,此时线程是已经自动占有了mutex。

3.例

1:两线程交替打印奇偶数

#include<iostream>
#include<thread>
#include<mutex>
using namespace std;
std::mutex data_mutex;
std::condition_variable data_var;
bool tag = true;
// odd : 1 
// even: 2
// odd : 3
// even : 4

void printodd() // 打印奇数
{
	std::unique_lock<std::mutex> ulock(data_mutex);
	for (int odd = 1; odd <= 100; odd += 2)
	{
		while (!tag)
		{
			data_var.wait(ulock);
		}
		cout << "odd: " << odd << endl;
		tag = false;
		data_var.notify_all();
	}
}
void printeven() //打印偶数
{
	std::unique_lock<std::mutex> ulock(data_mutex);

	for (int even = 2; even <= 100; even += 2)
	{	
		while (tag)
		{
			data_var.wait(ulock);//  1 2 
		}
		cout << "even: " << even << endl;
		tag = true;
		data_var.notify_all();
	}
}


int main()
{
	thread thodd(printodd);
	thread theven(printeven);

	thodd.join();
	theven.join();
	return 0;
}

参考文献:
C++多线程unique_lock详解

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值