前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >C++学习笔记-并发与多线程(1)

C++学习笔记-并发与多线程(1)

原创
作者头像
买唯送忧
修改2021-06-01 15:10:15
5520
修改2021-06-01 15:10:15
举报
文章被收录于专栏:虚拟技术学习虚拟技术学习

一、引入与线程

1、引入

我们知道,当一个可执行文件运行起来了,就产生了一个进程,而且进程里会含有一个主线程,这个时候主线程也会自动的开始运行,直到结束,主线程一结束,意味着这个进程也运行结束了。比如以下程序:

代码语言:javascript
复制
#include <iostream>
using namespace std;

void myPrint()
{
	for (int i = 1; i <= 10; i++) {
		cout << "我正在运行myprint()函数:-->" << i << endl;
	}
	cout << "我运行结束了" << endl;
	return;
}

int main()
{
	myPrint();
	return 0;
}

像这样的主线程就是从main函数触发,中途调用myPrint(),然后回到main函数,结束运行;

结果如下:

运行示意图如下:

myPrint函数只是主线程路上的一个环节
myPrint函数只是主线程路上的一个环节

2、线程

从上面的示意图可以看出来,这是一条路走到黑的运行方式,单纯的只有这种方式肯定是不行的,所以我们要引入线程,开辟几条路出来并发执行。C++11是支持线程库的,只需要#include <thread>,加入线程后代码如下:

代码语言:javascript
复制
#include <iostream>
#include <thread>
using namespace std;

void myPrint()
{
	for (int i = 1; i <= 10; i++) {
		cout << "我正在运行myprint()函数:-->" << i << endl;
	}
	cout << "我运行结束了" << endl;
	return;
}

int main()
{
    thread myThread(myPrint);//创建了线程,线程执行起点(入口)是myPrint;(2)执行线程
	for (int i = 1; i <= 10; i++) {
		cout << "我正在运行main()函数" << i << endl;
	}
	cout << "我运行完了主函数" << endl;
	return 0;
}

这样的代码运行结果如下:

看这输出不难发现,这代码运行的方式显然是如下的示意图:
看这输出不难发现,这代码运行的方式显然是如下的示意图:

可是这样是有问题的,看运行结果,,发现主线程比myPrint()线程早执行完,,(对于一个进程来说,如果主线程结束,会把所有的子线程销毁,所以就会产生报错,,那怎么办?一个方法,我们可以让主线程睡上一会儿:运行结果如下:

即在头文件添加#include <Windows.h>,然后在创建线程下面加入Sleep(1000);

3、join()函数

这样一看输出结果是准确的,但是还是会异常,因为子线程始终是没有和主线程汇合,从而导致异常。那要怎么解决呢?

这里就要引入join函数:join意为汇合,子线程和主线程回合,使用这个函数,主线程会被阻塞在这里,等待子线程结束,然后与子线程汇合;代码如下:

代码语言:javascript
复制
#include <iostream>
#include <thread>
using namespace std;

void myPrint()
{
	for (int i = 1; i <= 10; i++) {
		cout << "我正在运行myprint()函数:-->" << i << endl;
	}
	cout << "我运行结束了" << endl;
	return;
}

int main()
{
    thread myThread(myPrint);//创建了线程,线程执行起点(入口)是myPrint;(2)执行线程
	for (int i = 1; i <= 10; i++) {
		cout << "我正在运行main()函数" << i << endl;
	}
	cout << "我运行完了主函数" << endl;
	myThread.join();
	return 0;
}

运行结果如下:

如果把myThread.join();语句放在for循环上面就会先执行myPrint里面的循环,再执行main函数里的,,,
如果把myThread.join();语句放在for循环上面就会先执行myPrint里面的循环,再执行main函数里的,,,

4、detach()函数

主线程要在join()函数处阻塞,主线程越想越气,这样不公平,,确实,为了让主线程和子线程不汇合,而是各自结束,,那我们要引入:detach()函数,把上面代码的join改成detach()之后的运行结果

这里可以看到,主函数已经执行完了,而子线程基本上没有输出什么,,这是因为子线程被放到后台执行了,因此没有报错。

5、joinable()

joinable()判断是否可以成功使用join()或者detach()如果返回true,证明可以调用join()或者detach(),如果返回false,证明调用过join()或者detach(),join()和detach()都不能再调用了。

代码语言:javascript
复制
#include <iostream>
#include <thread>
using namespace std;

void myPrint()
{
	for (int i = 1; i <= 10; i++) {
		cout << "我正在运行myprint()函数:-->" << i << endl;
	}
	cout << "我运行结束了" << endl;
	return;
}

int main()
{
    thread myThread(myPrint);//创建了线程,线程执行起点(入口)是myPrint;(2)执行线程
    if (myThread.joinable())
	{
		cout << "可以调用可以调用join()或者detach()" << endl; //这里进入这个分支
	}
	else
	{
		cout << "不能调用可以调用join()或者detach()" << endl;
	}
	for (int i = 1; i <= 10; i++) {
		cout << "我正在运行main()函数" << i << endl;
	}
	cout << "我运行完了主函数" << endl;
	myThread.join();
    if (myThread.joinable())
	{
		cout << "可以调用可以调用join()或者detach()" << endl;
	}
	else
	{
		cout << "不能调用可以调用join()或者detach()" << endl; //这里进入这个分支
	}
	return 0;
}

二、其他线程的启动方式

只要是可调用对象,基本上都可以当成启动方式:这里介绍仿函数,成员函数和lamda表达式

1、仿函数(重载());

代码如下:

代码语言:javascript
复制
#include <iostream>
#include <thread>
using namespace std;

class Ta
{
private:
	int m_i;
public:
	void operator()() //不能带参数
	{
		for (int i = 0; i <= 10; i++) {
			cout << "传入的值"<< endl;
		}
	}
};
int main()
{
	Ta ta;
	thread myToj(ta);
	myToj.join();
	return 0;
}

运行结果如下:

如果把join()改成detach函数不应该会可能比子线程早执行完吗?如果比子线程早执行完,变量my和ta不应该被销毁了,,吗?

这是因为,类会执行拷贝构造,把变量复制一份到类里,证明如下:只需要写出来这几个函数

代码语言:javascript
复制
	//Ta(int &i) :m_i(i) { cout << "传入的值" << i << endl; }
	//Ta(const Ta& s) :m_i(s.m_i) { cout << "3333333333333333" << endl; }
	//~Ta() {cout << "ee" << endl;}
	

可以看到,拷贝构造函数的和构造函数的都输出了,析构函数执行了两次,一次就是原先的,一次就是之后拷贝构造生成。

2、成员函数:

调用形式如下:

代码语言:javascript
复制
#include <iostream>
#include <thread>
using namespace std;

class Ta
{
private:
	int m_i;
public:
	Ta(int &i) :m_i(i) { cout << "传入的值" << i << endl; }
	Ta(const Ta& s) :m_i(s.m_i) { cout << "3333333333333333" << endl; }
	~Ta() {cout << "ee" << endl;}
	void operator()() //不能带参数
	{
		for (int i = 0; i <= 10; i++) {
			cout << "传入的值"<< endl;
		}
	}
	void getMsg() {
		cout << "get the message" << m_i << endl;
	}
};

//调用形式改成:
	int my = 6;
	Ta ta(my);
	thread myToj(&Ta::getMsg, ta);
	myToj.join();
即可

3、lamda表达式

代码语言:javascript
复制
//调用形式为:
	
	auto lamdaThread = []()->void {
		cout << "我的线程开始运行" << endl;
		//-------------
		//-------------
		cout << "我的线程运行完毕" << endl;
	};
	thread myToj1(lamdaThread);
	myToj1.join();

三、有参数的线程

1、不通过子线程改变主线程值的方式,直接使用参数,不建议使用指针,可以使用临时对象,而在被调函数里,要加const,简单类型可以不加&

2、如果要通过子线程改变主线程值,要加std::ref(参数),被调方不需要加const;

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、引入与线程
    • 1、引入
      • 2、线程
        • 3、join()函数
          • 4、detach()函数
            • 5、joinable()
            • 二、其他线程的启动方式
              • 1、仿函数(重载());
                • 2、成员函数:
                  • 3、lamda表达式
                  • 三、有参数的线程
                    • 1、不通过子线程改变主线程值的方式,直接使用参数,不建议使用指针,可以使用临时对象,而在被调函数里,要加const,简单类型可以不加&
                      • 2、如果要通过子线程改变主线程值,要加std::ref(参数),被调方不需要加const;
                      领券
                      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档