并发与多线程

目录

第一节 并发基本概念及实现,进程,线程基本概念

(1)并发,进程,线程的基本概念和综述

(1.1)并发

(1.2)可执行程序

(1.3)进程

(1.4)线程

(2)并发的实现方法

(2.1)多进程并发

(2.2)多线程并发

(2.3)总结

(3)C++11新标准线程库

第二节 线程启动,结束,创建线程方法,join,detach

(1)范例演示线程运行的开始和结束

(1.1)thread

(1.2)join()

(1.3)detach()

(1.4)joinable()

(2)其他创建线程的方法

(2.1)用类,以及一个问题范例

第三节 线程传参详解,detach大坑,成员函数做线程函数

(1)传递零临时对象作为线程参数

(1.1)要避免的陷阱

(2)临时对象作为线程参数继续讲

(2.1)线程id概念

(2.2)零时对象构造时间抓捕

(3)传递类对象,智能指针作为线程参数

(4)用成员函数真做线程函数

第四节 创建多个线程,数据贡献问题分析,案例代码

(1)创建和等待多个线程

(2)数据共享问题分析

(2.1)只有读的数据

(2.2)有读有写

(2.3)其他案例

(3)共享数据的保护案例代码

第五节 互斥量概念,用法,死锁演示及解决详解

(1)互斥量(mutex)的基本概念

(2)互斥量的用法

(2.1)lock(),unlock()

(2.2)lock_quard类模板

(3)死锁

(3.1)死锁的演示

(3.2)死锁的一般解决方案

(3.3)lock函数模板

(3.4)lock_quard的adopt_lockd参数

第六节 unique_lock

(1)unique_lock取代lock_guard

(2)unique_lock的第二个参数

(2.1)adopt_lock

(2.2)try_to_lock

(2.3)defer_lock

(3)unique_lock成员函数

(3.1)lock

(3.2)unlock

(3.3)try_lock

(3.4)release

(4)unique_lock所有权的传递



第一节 并发基本概念及实现,进程,线程基本概念

(1)并发,进程,线程的基本概念和综述

(1.1)并发

并发:两个或多个独立的任务同时发生,也就是一个程序需要同时执行多个独立的任务。

单核cpu:对于两个任务,计算机先执行a任务10ms,再去执行b任务10ms,再执行a任务10ms这种情况,且在切换过程中也会有消耗。但是,这种执行方式并不是真正的并发,也叫上下文切换

多核cpu:对于两个任务如果存在两个甚至3个cpu,那么就可以一个cpu处理一个任务。

实际开发中,往往cpu是少于任务数的,因此还是按单核处理方式,每个cpu不同时间处理不同任务,以达到效果。这样能够“同时”处理不同的事情,加快了处理时间。

(1.2)可执行程序

window:一个为exe结尾的文件

linux:具有rwxrwxrwx权限的文件

(1.3)进程

进程:一个可执行程序运行起来就是一个进程

(1.4)线程

每个进程都有一个主线程,可执行程序是通过主线程完成的,主线程和进程生死相依。

当创建了一个主线程时,可以人为加入线程,并且加入的线程可以执行不同的任务。

总结:

a.线程是用来执行代码的,一条线程代表执行的一条通路

b.一个进程包含一个主线程,可以人为额外加入线程

(2)并发的实现方法

a.多个进程实现并发

b.单个进程下多个线程并发

(2.1)多进程并发

也就是开启了两个程序,例如word和IE浏览器这种

(2.2)多线程并发

在一个进程中开了多个线程,线程之间共享内存,且对全局变量可以一起使用,所以使用多线程的资源消耗小于多进程。此外,共享内存也会存在数据一致性问题,例如线程A的数据会覆盖线程B

(2.3)总结

a.线程启动更快,更轻量级,消耗资源更少,速度更快

b.缺点是难度较大,要解决数据一致性问题

(3)C++11新标准线程库

之前创建线程的缺点;不能跨平台

window:CreateThread(),_beginThread(),_beginThreadxe()

linux:pthread_create()

现在:增加了C++语言对多线程的支持,具有可移植性

第二节 线程启动,结束,创建线程方法,join,detach

(1)范例演示线程运行的开始和结束

a)包含头文件《thread》

b)有一个子线程函数

c)main中开始执行代码

d)主线程和子线程是两个不同的线去执行,其中一个不会“阻挡”另外一个

一般情况下:想保持子线程运行就不能终止主线程。

// thread和join的使用

#include 
#include
using namespace std;

void myPrint() {
	cout << "子线程开始" << endl;

	cout << "子线程结束" << endl;
}

int main()
{
	thread myjob(myPrint);//创建了线程,且线程入口是myPrint()
	myjob.join();//阻塞主线程,让主线程等待子线程结束,然后子线程和主线程会和,然后再主线程

	cout << "这是主线程" << endl;

}

 

特殊情况下:主线程结束,子线程还在运行(主线程和进程不一样)


#include 
#include
using namespace std;

void myPrint() {
	cout << "子线程开始" << endl;

	cout << "子线程结束" << endl;
}

int main()
{
	thread myjob(myPrint);//创建了线程,且线程入口是myPrint()
	//myjob.join();//阻塞主线程,让主线程等待子线程结束,然后子线程和主线程会和,然后再主线程
	myjob.detach();//子线程被运行时库接管,由系统释放且被称为守护进程

	cout << "主线程结束" << endl;

}

 

 进程结束了,一切都结束了。

(1.1)thread

一个标准库的类,用来创建线程。当创建好后就会执行子线程了。

(1.2)join()

用来阻塞主线程,如果没有join就会导致子线程还没有结束,程序又去执行了主线程,然后程序又执行子线程这种错误输出情况。

(1.3)detach()

a)主线程和子线程各自运行,且主线程和子线程不再汇合,主线不用再等待子线程,主线程可以先结束,且不影响子线程。但也不能再人为管这个子线程了

b)存在detach是因为子线程太多了,让主线程等不划算。

c)一但调用detach就不能调用join

(1.4)joinable()

判断是否可以成功使用join和detach,返回true或者false

	if (myjob.joinable()) {
		cout << "可以join" << endl;
	}
	else {
		cout << "不可以join" << endl;
	}

(2)其他创建线程的方法

(2.1)用类,以及一个问题范例

#include 
#include
using namespace std;

class TA {
public:
	void operator()() {
		cout << "子线程开始" << endl;

		cout << "子线程结束" << endl;
	}
};

int main()
{
	TA ta;
	thread myjob(ta);//ta对象被复制到了线程,且线程入口是ta对象()
	myjob.join();//阻塞主线程,让主线程等待子线程结束,然后子线程和主线程会和,然后再主线程
	cout << "主线程结束" << endl;

	return 0;
}

第三节 线程传参详解,detach大坑,成员函数做线程函数

(1)传递零临时对象作为线程参数

#include 
#include
using namespace std;

//void myPrint(const int &i, char* pmybuf) {
//
//	cout <

(1.1)要避免的陷阱

a)使用detach时不能有引用和指针,因为当进程有可能先结束,内存被回收了,那么线程会出问题

b)引用:可能出现问题

c)指针:一定出现问题(除非用string接)

d)解决办法,对于数组,可以直接在字符数组调用前利用string转换,也就是利用临时对象

(2)临时对象作为线程参数继续讲

(2.1)线程id概念

a)主线程和子线程都对应一个id且不相同。

b)可以通过get_id()获取

(2.2)零时对象构造时间抓捕

(3)传递类对象,智能指针作为线程参数

(4)用成员函数真做线程函数

第四节 创建多个线程,数据贡献问题分析,案例代码

(1)创建和等待多个线程

// ConsoleApplication1.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include 
#include
#include
using namespace std;

//void myPrint(const int &i, char* pmybuf) {
//
//	cout <join();//等待10个线程返回
	}
	cout << "主线程结束" << endl;
	return 0;
}

a)多个线程执行顺序是乱的,这和操作系统本身资源调度有关系 

(2)数据共享问题分析

(2.1)只有读的数据

共享数据:每个线程都可以读

// ConsoleApplication1.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include 
#include
#include
using namespace std;

vectorvec = { 1,2,3 };

void myPrint(int num) {

	cout << "id= "< mythread;


	for (int i = 0; i < 10; i++) {
		mythread.push_back(thread(myPrint, i));//创建10个线程,且开始执行
	}
	for (auto it = mythread.begin(); it != mythread.end(); it++) {
		it->join();//等待10个线程返回
	}
	cout << "主线程结束" << endl;
	return 0;
}

并发与多线程_第1张图片 虽然线程输出时间不一样,但是每个线程获取的值一样 

(2.2)有读有写

简单办法:读的时候不能写,写的时候不能读,并且写的读和写的线程也不能单独一起执行,只能单个执行

(2.3)其他案例

比如火车订票,一张票不能同时给两个人

(3)共享数据的保护案例代码

网络游戏服务器。一个线程收集玩家命令,存入队列中

                             一个线程从队列中取出玩家命令,并执行

关共享数据中的互斥量用来保护

第五节 互斥量概念,用法,死锁演示及解决详解

(1)互斥量(mutex)的基本概念

a)为了保护共享数据,用代码把共享数据锁住,其他想操作共享数据的必须等待。

b)互斥量是一个类对象。可以理解为一把锁,线程想要使用这个数据,就要给这个数据上锁,如果上锁成功也就是lock返回了,否则无法上锁则无法使用该数据

c)互斥量保护数据要小心,保护数据过多导致效率降低,少了则没有效果

(2)互斥量的用法

(2.1)lock(),unlock()

a)步骤:lock,操作共享数据,unlock

b)lock和unlock必须成对使用



#include 
#include
#include
#include
#include

using namespace std;

vectorvec = { 1,2,3 };

void myPrint(int num) {

	cout << "id= "<msgRecQueue;
	mutex my_mutex;

};

int main()
{
	A myjob;
	thread myOutMsgObj(&A::outMsgRecQueue,&myjob);
	thread myInMsgObj(&A::inMsgRecQueue, &myjob);
	myInMsgObj.join();
	myOutMsgObj.join();
	
	多线程执行
	//vector mythread;
	//for (int i = 0; i < 10; i++) {
	//	mythread.push_back(thread(myPrint, i));//创建10个线程,且开始执行
	//}
	//for (auto it = mythread.begin(); it != mythread.end(); it++) {
	//	it->join();//等待10个线程返回
	//}
	//cout << "主线程结束" << endl;

	return 0;
}

(2.2)lock_quard类模板

a)程序员忘记umlock,lock_quard帮你unlock,类似智能指针,忘记释放不要紧,给你释放

b)lock_guard直接取代lock和unlock,你用了lock_guard之后不能用lock和unlock

c)lock_guard的构造函数执行了lock

d)lock_guard的析构函数执行了unlock

e)可以通过{}来决定构造和析构的位置

	bool OutMsgLULProc(int &command)
	{
		lock_guard sbguard(my_mutex);
		//my_mutex.lock();
		if (!msgRecQueue.empty()) {
			//消息不为空,取出数据
			int command = msgRecQueue.front();//返回第一个元素
			msgRecQueue.pop_front();//移除队列第一个元素
			//根据命令进行处理
			//my_mutex.unlock();
			return true;

		}
		//my_mutex.unlock();
		return false;
	}

(3)死锁

a)你不录取我,我一直等你,你不来我公司,我一直等你

b)一个互斥量是一把锁。死锁必须至少有两把锁(两个互斥量)

c)在C++中,线程A需要先lock锁1,再lock锁2.线程B需要先lock锁2,再lock锁1.当Alock了锁1准备lock锁2时,发生了上下文切换此时Block了锁2。当B准备lock锁1时就发生了死锁,因为B不能lock锁1,此时锁1正被Alock了

(3.1)死锁的演示

lock有顺序,unlock没有顺序

class A {
public:
	//把收到的玩家命令存入队列
	void inMsgRecQueue() {
		for (int i = 0; i < 10000; ++i) {
			cout << "inMsgRecQueue执行,插入第一个元素" << i << endl;
			//开启保护
			my_mutex1.lock();
			my_mutex2.lock();
			msgRecQueue.push_back(i);//i代表玩家命令,插入消息队列
			//关闭保护
			my_mutex2.unlock();
			my_mutex1.unlock();
		}
		return;
	}
	bool OutMsgLULProc(int &command)
	{
		//lock_guard sbguard(my_mutex);
		my_mutex2.lock();
		my_mutex1.lock();
		if (!msgRecQueue.empty()) {
			//消息不为空,取出数据
			int command = msgRecQueue.front();//返回第一个元素
			msgRecQueue.pop_front();//移除队列第一个元素
			//根据命令进行处理
			//my_mutex.unlock();
			my_mutex2.unlock();
			my_mutex1.unlock();
			return true;

		}
		//my_mutex.unlock();
		my_mutex2.unlock();
		my_mutex1.unlock();
		return false;
	}

(3.2)死锁的一般解决方案

a)保证两个互斥量上锁顺序一致

b)lock_guard顺序一致也可以

(3.3)lock函数模板

a)能力:一次性可以锁住多个互斥量,不限互斥量的数量,互斥量顺序无所谓。

b)不会存在因为锁的顺序问题导致死锁,因为其实现本质是如果lock了锁1准备lock锁2出现问题,则直接unlock了锁1。

c)如果只剩一个没lock,它就会等着

lock(my_mutex2, my_mutex1);

(3.4)lock_quard的adopt_lockd参数

    lock(my_mutex1, my_mutex2);
	lock_guard sbguard1(my_mutex1, adopt_lock);
	lock_guard sbguard2(my_mutex2, adopt_lock);

adopt_lock作用表示这个互斥量已经lock了,不需要再通过lock_guard的构造函数lock

第六节 unique_lock

是一个类模板

(1)unique_lock取代lock_guard

比lock_guard灵活,但效率差,内存消耗多

(2)unique_lock的第二个参数

(2.1)adopt_lock

a)adopt_lock作用表示这个互斥量已经lock了,不需要再通过lock_guard的构造函数lock,换句话说,使用这个参数你必须提起lock

b)使用方法和lock_guard一样

my_mutex1.lock();
unique_lock sbguard1(my_mutex1,adopt_lock);

(2.2)try_to_lock

a)尝试去lock

b)不能先lock,如果有了lock再try_to_lock会卡死

unique_lock sbguard1(my_mutex1, try_to_lock);
if (sbguard1.owns_lock()) {
	//成功拿到锁
		msgRecQueue.push_back(i);//i代表玩家命令,插入消息队列
	}
else {
	//没拿到锁
	cout << "msgRecQueue没拿到锁" << endl;
}

(2.3)defer_lock

不能自己先lock,否则会报异常。defer_lock代表初始化了一个没有加锁的mutex

(3)unique_lock成员函数

(3.1)lock

(3.2)unlock

(3.3)try_lock

(3.4)release

(4)unique_lock所有权的传递


暂停更新!!!

你可能感兴趣的:(C++,java,jvm,数据库)