c++基础——多线程

代码片段管理(自制脚本)

  1. 工具菜单 ——>代码管理器
  2. 随便拿一个脚本,自己用记事本打开,修改后另存为一个脚本,再导入到myCode即可

并发、进程、线程的基本概念

  1. 核心的应用技术(编程通用性技术,不同语言的,接口可能不一样)
    数据库编程
    网络编程
    多线程编程
  2. 并发概念:两个或者多个独立活动同时进行(官方解释)
    【通常做法:先吃饭,再去上厕所,或者先上厕所,再去吃饭】
    【蹲在马桶上吃饭】
    并发假象:以前的电脑单核采用上下文的形式进行实现的并发(比如:吃饭的几个步骤和上厕所的几个步骤交替进行)
  3. 进程概念:
    (1)计算机中的程序关于某一个数据集合上的一次运行活动(exe的运行的状态)
    (2)每一个进程都有一个主线程并且只有一个主线程(类似,每个程序只有一个main函数,不能有多个main函数)。
    (3)vs编译器中按ctr+F5运行程序本质其实就是主线程调用main函数的代码。
  4. 线程的概念:
    线程其实就是一个代码的运行通道。自己可以创建多个线程(多个执行通道(路径))。
    例子:长沙->哈尔滨
    长沙->北京->哈尔滨
  5. 并发实现
    (1)多进程实现并发:主要是进程间通信
    1、一个电脑上,管道、文件、消息队列、内存共享
    2、socket网络通信实现

    (2)单个进程,多个线程实现并发(就是一个主线程,多个子线程)

线程多种创建方式

1、需要包含c++多线程库

#include <thread>

2、创建线程:thread类去创建一个线程——需传递一个函数指针而已
注意:创建线程不做处理,就会调用abort终止程序

#include <iostream>
#include <thread>
using namespace std;
// 吃饭子线程
void print()
{
	cout<<"吃饭的过程!"<<endl;
}
int main()
{
	// 创建线程(一个吃饭的线程):通过普通函数去创建线程
	thread test(print);	// 传递一个函数指针就可
	
	// 主线程
	cout << "散步的过程!...." <<endl;
	return 0;
}
// 会报abort()错误!
// 执行过程:散步->吃饭。主线程已经结束,子线程还在继续!引发中断!因为子线程是依附在主线程上的。

在这里插入图片描述
3、处理线程
(1)join()函数:汇合线程,阻塞主线程。(让主线程等待子线程运行完成后,才继续执行主线程)————线程阻塞

#include <iostream>
#include <thread>
using namespace std;
// 吃饭子线程
void print()
{
	cout<<"吃饭的过程!"<<endl;
}
int main()
{
	// 创建线程(一个吃饭的线程):通过普通函数去创建线程
	thread test(print);	// 传递一个函数指针就可
	test.join();
	// 主线程
	cout << "散步的过程!...." <<endl;
	return 0;
}
// 执行过程:吃饭->散步。

在这里插入图片描述
(2)detach()函数:分离,打破依赖关系,把子线程编程 驻留 后台(主线程如果先执行完,已结束,子线程在后台慢慢执行完成,这可能看不到子线程的执行结果)。

#include <iostream>
#include <thread>
using namespace std;
// 吃饭子线程
void print()
{
	cout<<"吃饭的过程!"<<endl;
}
int main()
{
	// 创建线程(一个吃饭的线程):通过普通函数去创建线程
	thread test(print);	// 传递一个函数指针就可
	test.detach();
	// 主线程
	cout << "散步的过程!...." <<endl;
	return 0;
}
// 执行结果:散步,看不到吃饭(可能)。

在这里插入图片描述
加入等待函数Sleep()

#include <iostream>
#include <thread>
#include <windows.h>
using namespace std;
// 吃饭子线程
void print()
{
	cout<<"吃饭的过程!"<<endl;
}
int main()
{
	// 创建线程(一个吃饭的线程):通过普通函数去创建线程
	thread test(print);	// 传递一个函数指针就可
	test.detach();
	Sleep(5000);
	// 主线程
	cout << "散步的过程!...." <<endl;
	return 0;
}
// 执行结果:散步->吃饭。

在这里插入图片描述
注意点:一个线程只能被处理一次,如果你join就不能detach,如果你detach就不能join

(3)joinable: 判断当前线程是否可以被detach或者join,可以返回true,不可以返回false。

#include <iostream>
#include <thread>
using namespace std;
// 吃饭子线程
void print()
{
	cout<<"吃饭的过程!"<<endl;
}
int main()
{
	// 创建线程(一个吃饭的线程):通过普通函数去创建线程
	thread test(print);	// 传递一个函数指针就可
	test.detach();
	if(test.joinable())
	{
		test.join();
	}
	else
	{
		cout<<"当前线程已经被处理!"<<endl;
	}
	// 主线程
	cout << "散步的过程!...." <<endl;
	return 0;
}
// 执行结果:当前线程已经被处理!
// 散步-》吃饭。

在这里插入图片描述

其他的几种线程创建方式

1、通过普通函数创建线程
2、通过类的对象创建线程
仿函数:类的对象模仿函数的行为。本质就是重载()运算符(就是使一个类的对象使用看上去像一个函数。其实现就是类中实现一个operator(),这个类就有了类似函数的行为,就是一个仿函数类了)

#include <iostream>
using namespace std;
class MM
{
public:
	MM()
	{
		cout<<"调用构造函数"<<endl;
	}
	// 仿函数:operator()为函数名
	void operator()()
	{
		cout<< "子线程启动"<<endl;
	}
};
int main()
{
	// 仿函数的调用
	MM mm;
	//(1)调用函数的方式实现仿函数调用
	mm.operator()();
	
	//(2)重载
	mm();	// 也会调用重载函数
	
	//(3)创建无名对象直接调用仿函数
	MM();	// 但是会被解析成调用构造函数
	
	//故而,改为如下:
	MM{}();	// 注意!!!首先会调用构造函数,然后调用重载函数
	return 0;
}

在这里插入图片描述

#include <iostream>
#include <thread>
using namespace std;
class MM
{
public:
	MM()
	{
		cout<<"调用构造函数"<<endl;
	}
	// 仿函数:operator()为函数名
	void operator()()
	{
		cout<< "子线程启动"<<endl;
	}
};
int main()
{
	MM mm;
	thread test(mm);	// 类似thread test(mm())
	test.join();
	
	thread test2((MM()));// test2(MM())解析问题 解析为函数调用,怎么处理问题,用()
	test2.join();
	cout<<"主线程..."<<endl;
	return 0;
}
// 结果:调用构造函数->子线程启动->调用构造函数->子线程启动->主线程...

在这里插入图片描述

3、Lambda表达式的方式创建线程

#include <iostream>
#include <thread>
using namespace std;
// 求最大值
int Max(int a, int b)
{
	return a>b ? a : b;
}
int main()
{
	// Lambda表达式:返回的是一个函数指针(并且当前函数的定义也一并完成)——函数的定义变成一个表达式
	// []()mutable throw->返回值类型{函数体}——(捕获的方式,函数参数,是否能够修改(mutable表示能修改),是否存在异常,是否存在返回值(指定返回值类型),函数体)
	//[]捕获值:使用外部变量的方式
	//[this] 地址指针,一般是类的对象
	//[=] 值的方式
	//[] 不使用外部的值
	//[&] 引用的方式
	//[&a,=] 组合的方式
	// 非常完整版本的Lambda
	int (*pFunc)(int,int) = NULL;	// 函数指针
	pFunc = [](int a, int b)mutable throw()->int{ return a>b ? a : b; };
	// throw():不存在异常

	auto pFunction = [](int a, int b)mutable throw()->int{ return a>b ? a : b; };
	cout << pFunction(1, 2) << endl;

	cout << [](int a, int b)mutable throw()->int{ return a>b ? a : b; }(1, 2) << endl;

	cout << [](int a, int b){return a>b ? a : b; }(1, 2) << endl;

	thread test([]{cout << "子线程启动" << endl; });
	test.join();

	cout << "主线程..." << endl;
	return 0;
}
// 结果:2
//2
//2
//子线程启动->主线程...

在这里插入图片描述

4、带参的方式创建线程

#include <iostream>
#include <thread>
using namespace std;
// 打印函数——传值的方式,不是传引用的方式
void printData(int num)
{
	num++;
	cout<<"子线程:"<<num <<"启动"<<endl;
}
// 打印函数——传引用的方式
void printValue(int &num)
{
	num++;
	cout<<"子线程:"<<num <<"启动"<<endl;
}

// 只能传一个常量进来
void print(int &&a)
{

}
int main()
{
	int num=0;
	int value=0;
	thread test(printData,num);
	test.join();
	
	thread test2(printValue,ref(value));	// 不管是&value,还是value都会出错。用ref()
	test2.join();
	
	cout<<"主线程结束:"<<num<<"..."<<endl;
	cout<<"主线程结束:"<<value<<"..."<<endl;
	return 0;
}
// 结果:子线程1启动->子线程1启动->主线程结束:0...->主线程结束:1...

在这里插入图片描述

5、带智能指针的方式创建线程

#include <iostream>
#include <thread>
using namespace std;
void print(unique_ptr<int> ptr)
{
	// 获取线程地址
	cout<<"子线程:"<<ptr.get()<<endl;
	// 获取线程id
	cout<<"id"<<this_thread::get_id()<<endl;
}
int main()
{
	//1、智能指针:能自动管理申请的内存,不需要手动释放内存,会记录对象的个数
	// 获取地址:get()
	unique_ptr<int> ptr(new int(100));// 用管理int型的智能指针对象ptr 去管理 一个对象(这个对象无名称,其初始化的值为100)
	cout<<"访问智能指针对象中管理对象的地址:"<<ptr.get()<<endl;	// 获取了ptr对象中管理对象的地址
	cout<<"访问智能指针对象中管理对象的值:"<<*ptr.get()<<endl; // 结果为100
	
	thread test(print,move(ptr));	// 使用ptr会出错,用移动语义move()
	test.join();

	cout<<"主线程id:"<<this_thread::get_id()<<endl;
	cout<<"访问智能指针对象中管理对象的地址:"<<ptr.get()<<endl;
	return 0;
}
// 结果:访问智能指针对象中管理对象的地址:....(地址)
// 访问智能指针对象中管理对象的值:100
// 子线程:....(地址)
// id:....(子线程id)
// 主线程id:....(主线程id)
// 访问智能指针对象中管理对象的地址:....(地址:00000000,与第一次结果不一样,因为move将ptr移动到了子线程,而主线程的ptr无效了)

在这里插入图片描述

6、通过类的成员函数创建线程

#include <iostream>
#include <thread>
using namespace std;
class MM
{
public:
	void print(int &num)
	{
		num = 100;
		cout << "子线程:" << this_thread::get_id() << endl;
	}
};
int main()
{
	MM mm;
	int num = 1001;
	//用类的成员函数成为线程处理函数的时候,一定要先告诉别人是哪个对象的成员函数
	thread test(&MM::print, mm, ref(num));// &成员函数的指针,哪个对象——mm对象,参数;注意:多了一个参数——指定对象!!!
	test.join();

	cout << "主线程:" << this_thread::get_id() << endl;
	cout << num << endl;
	return 0;
}
// 结果:子线程:id——>主线程:id-->100

在这里插入图片描述

资源竞争问题——加锁过程

#include <iostream>
#include <thread>
#include <list>
using namespace std;
/*
	资源竞争问题:海王的故事
*/
class Seaking
{
public:
	void makeGirFriend()
	{
		for(int i=0;i<1000;i++)
		{
			cout<<"喜得一枚女盆友"<<i<<endl;
			mm.push_back(i);
		}
	}
	void breakUp()
	{
		for(int i=0;i<1000;i++)
		{
			if(!mm.empty())
			{
				cout<<"失去一枚女盆友"<<i<<endl;
				mm.pop_front();
			}
			else
			{
				cout<<"都分手了,孤独终老"<<endl;
			}
		}
	}
protected:
	list<int> mm;
};
int main()
{
	Seaking seaking;
	thread test1(&Seaking::makeGirFriend,&seaking);
	thread test2(&Seaking::breakUp,&seaking);
	test1.join();
	test2.join();
	cout<<"主线程结束......"<<endl;
}
// 结果:
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值