当前位置:   article > 正文

C++ Thread

c++ thread

线程的创建

void funa()
{
	cout << "funa()" << endl;
}
void funb(int a)
{
	cout << "funb(int a)" << endl;
}
void func(int& a)
{
	a += 10;
	cout << "func(int& a)" << endl;
}
void fund(int* p)
{
	if (p == nullptr) return;
	*p += 100;
	cout << "fund(int* p)" << endl;
}

int main()
{
	int x = 10;
	std::thread tha(funa);
	std::thread thb(funb, x);
	std::thread thc(func, std::ref(x));//需要引用不能直接给,需要ref告诉其为引用类型
	std::thread thd(fund, &x);

	tha.join(); //等待其他线程结束
	thb.join();
	thc.join();
	thd.join();
	cout << x << endl;

	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36

detach 句柄独立

容许线程从线程句柄独立开来执行

int main()
{
	std::thread tha(funa);
	tha.detach(); //主线程与开辟线程没有关系,主线程可以首先结束
	cout << "main end" << endl;
	return 0;
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

主线程结束会将资源剥夺,所有尽量不要进行该操作

线程资源转移

int main()
{
	std::thread tha(funa);
	std::thread thb(std::move(tha));
	//std::thread thb(tha); error!!!
	//thb = tha;
	std::thread thc;
	thc = std::move(thb);


	thc.join();

	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

不能通过拷贝构造与赋值语句进行线程之间的转换,只能使用移动构造以及移动赋值进行资源转移

sleep_ for

使当前线程的执行停止指定的时间段

int funa(int n,int &x )
{
	for (int i = 1; i <= n; ++i)
	{
		cout << "thread funa" << endl;
		std::this_thread::sleep_for(std::chrono::milliseconds(1000));
	}
	x = n;
	cout << "thread funa end" << endl;
	return n;
}
//C++无法获得线程的返回值
int main()
{	
	int a = 0; //但是可以通过引用的方式进行获取
	thread tha(funa, 5, std::ref(a));
	tha.join();
	cout << "main end" << endl;
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

在这里插入图片描述

int funa(int n,int &x )
{
	for (int i = 1; i <= n; ++i)
	{
		cout << "thread funa" << endl;
		std::this_thread::sleep_for(std::chrono::milliseconds(1000));
	}
	x = n;
	cout << "thread funa end" << endl;
	return n;
}
//C++无法获得线程的返回值
int main()
{	
	int a = 0; //但是可以通过引用的方式进行获取
	thread tha(funa, 5, std::ref(a));
	tha.join();
	tha.detach();
	cout << "main end" << endl;
	return 0;
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

这样会导致,主线程与子线程分离,子线程根本无法结束
在这里插入图片描述

全局函数线程化

class Object
{
private:
	int value;
public:
	Object(int x = 0):value(x){}
	~Object() {}

	void run()
	{
		cout << "Object run" << endl;
	}
};
int main()
{
	Object obj;
	std::thread thobj(&Object::run, &obj);
	thobj.join();
	cout << "main end" << endl;
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

Object::run 线程调用,需要加上&符号,并且传入一个this指针
在这里插入图片描述
静态全局函数

class Object
{
private:
	int value;
public:
	Object(int x = 0):value(x){}
	~Object() {}

	void run()
	{
		cout << "Object run" << endl;
	}
	static void fun(int x )
	{
		Object obj(x);
		obj.run();
		cout << "static fun" << endl;
	}
};
int main()
{
	std::thread tha(Object::fun, 5);
	tha.join();
	cout << endl;
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

静态成员函数,线程调用不需要传参this指针,并且可以通过线程调动静态成员的方式去另向调动普通成员函数在这里插入图片描述

joinable

检查线程是否可合并,即潜在地运行于平行环境中,也就是线程是否于某一个函数资源进行关联

void foo()
{
	std::this_thread::sleep_for(std::chrono::seconds(1));
	cout << "foo end" << endl;
}

int main()
{
	std::thread t;
	cout << "befor starting,joinable" << t.joinable() << endl;
	t = std::thread(foo); //无名对象线程 移动赋值
	cout << "after starting,joinable" << t.joinable() << endl;
	t.join();
	cout << "after joining" << t.joinable() << endl;
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

hardware_concurrency

int main()
{
	cout << t.hardware_concurrency() << endl;//返回CPU内核数
	cout << std::thread::hardware_concurrency() << endl;
}
  • 1
  • 2
  • 3
  • 4
  • 5

返回CPU当前逻辑处理数

线程的互斥

int g_num = 0;
void print(int id)
{
	for (int i = 0; i < 5; ++i)
	{
		++g_num;
		cout << "id:" << id << "==>" << g_num << endl;
		std::this_thread::sleep_for(std::chrono::seconds(2));
	}
}
int main()
{
	std::thread tha(print, 0);
	std::thread thb(print, 1);
	tha.join();
	thb.join();
	cout << "main end" << endl;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

原子操作的每次执行并不一定执行结果相同
在这里插入图片描述
在这里插入图片描述

原子操作定义

我们通过原子操作进行定义g_num

std::atomic_int  g_num = 0;
  • 1

在这里插入图片描述
可以保证每次结果相同

异变关键字不能解决原子操作的问题

互斥锁

int g_num = 0;
std::mutex mtx;
void print(int id)
{
	for (int i = 0; i < 5; ++i)
	{
		mtx.lock();
		++g_num;
		cout << "id:" << id << "==>" << g_num << endl;
		mtx.unlock();

		std::this_thread::sleep_for(std::chrono::seconds(1));
	}
}
int main()
{
	std::thread tha(print, 0);
	std::thread thb(print, 1);
	tha.join();
	thb.join();
	cout << "main end" << endl;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

在这里插入图片描述
互斥锁只要解决异步操作中对同一个资源的竞争

try_lock

void print(int id)
{
	for (int i = 0; i < 5; ++i)
	{
		while (!mtx.try_lock()) //试图加锁,加锁失败也会执行
		{	//加锁失败则一直循环
			cout << "lock..." << endl;
		}
		++g_num;
		cout << "id:" << id << "==>" << g_num << endl;
		mtx.unlock();

		std::this_thread::sleep_for(std::chrono::seconds(1));
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

try_lock 加锁成功会返回1,失败返回0,倘若加锁失败它依旧会向下执行,所以一般使用会将其于while循环嵌套,则该锁会在无法获得加锁的情况下不断地尝试加锁

recursive_mutex 递归锁

递归锁,提供能被同一线程递归锁定的互斥设施

在这里插入图片描述
如上图,当我们进入线程,去调动第一个函数进行加锁,随机递归进入第二个线程,继而又进行了加锁,而普通锁进行两次加锁则会出现错误

而当我们使用了递归锁,则在递归过程中再次遇到加锁的时候,则该锁失效直接进入函数,并且每遇到一次锁都会又一次相应的解锁

lock_guard

实现严格基于作用域的互斥体所有权包装器

void print(int id)
{
	for (int i = 0; i < 5; ++i)
	{
		std::lock_guard<std::mutex> lock(mtx); //对象构造 内部调用互斥锁
		++g_num;
		cout << id << "-->" << g_num << endl;

		std::this_thread::sleep_for(std::chrono::milliseconds(200));
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

为在作用域块期间占用互斥提供便利RAII风格机制,与智能指针相似

而在上面代码中,作用域块就是for语句,则当for语句结束,该对象进行析构继而就进行解锁

条件变量 condition_variable

condition_variable 类是同步原语,能用于阻塞一个线程,或同时阻塞多个线程,直至另一线程修改共享变量,并通知condition_variable

std::mutex mx;               //互斥量
std::condition_variable cv;  //条件变量
int isReady = 0;             //全局变量

void print_A()
{
	std::unique_lock<std::mutex> lock(mx);
	int i = 0;
	while (i < 10)
	{
		while (isReady != 0)
		{
			cv.wait(lock);//等待
		}
		cout << "A" << endl;
		isReady = 1;
		++i;
		std::this_thread::sleep_for(std::chrono::milliseconds(100));
		cv.notify_all();  //唤醒所有等待线程
	}
}
void print_B()
{
	std::unique_lock<std::mutex> lock(mx);
	int i = 0;
	while (i < 10)
	{
		while (isReady != 1)
		{
			cv.wait(lock);//等待
		}
		cout << "B" << endl;
		isReady = 2;
		++i;
		std::this_thread::sleep_for(std::chrono::milliseconds(100));
		cv.notify_all();  //唤醒所有等待线程
	}
}

void print_C()
{
	std::unique_lock<std::mutex> lock(mx);
	int i = 0;
	while (i < 10)
	{
		while (isReady != 2)
		{
			cv.wait(lock);//等待
		}
		cout << "C" << endl;
		isReady = 0;
		++i;
		std::this_thread::sleep_for(std::chrono::milliseconds(100));
		cv.notify_all();  //唤醒所有等待线程
	}
}

int main()
{
	thread tha(print_A);
	thread thb(print_B);
	thread thc(print_C);
	
	tha.join();
	thb.join();
	thc.join();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67

这样就可以达成,ABC不断循环一个一个打印

当B,C线程进入调用函数,在条件变量不符合情况下,进入wait队列,线程阻塞并且将锁状态释放

直到A线程进入函数,并且由于BC线程在阻塞后将锁状态释放,A线程获得互斥锁,接着判断条件变量符合,不执行等待,进行打印,接着唤醒在等待队列上的所有线程

BC线程被唤醒,C回到被阻塞位置继续执行,并且获得锁,接着由于条件变量不符,又回到了阻塞状态

B线程回到被阻塞位置,获得锁,条件变量符合进行打印,唤醒在等待队列的所有线程

惊群现象

当我们的阻塞队列中拥有较多线程的时候,当我们进行唤醒的时候,将所有阻塞队列的线程都进行唤醒,并且当互斥锁释放后,被唤醒的所有线程都开始抢占这个互斥锁,但是仅仅只有一个线程可以抢占,其他的则又进行等待

notify_one 则是一次仅能唤醒一个线程用来避免惊群现象

条件变量判断

void print_A()
{
	std::unique_lock<std::mutex> lock(mx);
	int i = 0;
	while (i < 10)
	{
		if (isReady != 0)
		{
			cv.wait(lock);//等待
		}
		cout << "A" << endl;
		isReady = 1;
		++i;
		std::this_thread::sleep_for(std::chrono::milliseconds(100));
		cv.notify_all();  //唤醒所有等待线程
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

若我们将原本第二个while修改为if判断,那么假如当A线程唤醒等待线程并且释放锁,BC线程在进入到原先位置继续运行,则会直接进入下一步进行打印,就会导致不能严格按照ABC顺序进行打印

void print_A()
{
	std::unique_lock<std::mutex> lock(mx);
	int i = 0;
	while (i < 10)
	{
		while (isReady != 0)
		{
			cv.wait(lock);//等待
		}
		cout << "A" << endl;
		isReady = 1;
		++i;
		std::this_thread::sleep_for(std::chrono::milliseconds(100));
		cv.notify_one();  
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

若我们将原先唤醒所有线程修改为仅仅唤醒一个线程,那么会导致若我们需要下一次执行B线程,而C线程先抢占到了互斥锁,那么会导致C再次回到等待队列,C线程无法去执行唤醒操作,导致所有线程都进入到了阻塞队列

unique_lock 可移植互斥锁

std::mutx mx;
std::condition_variable cv;
int number = 1;
void Print_A()
{
	unique_lock<std::mutex> lock(mx);
	unique_lock<std::mutex> lc(std::move(lock));
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

可以将锁的拥有权进行转移

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/2023面试高手/article/detail/153498
推荐阅读
相关标签
  

闽ICP备14008679号