C++11并发与多线程笔记(2)线程启动、结束,创建线程多法、join,detach

1、线程创建方法及案例

  • 程序运行起来,生成一个进程,该进程所属的主线程开始自动运行;当主线程从main()函数返回,则整个进程执行完毕。
  • 主线程从main()开始执行,那么我们自己创建的线程,也需要从一个函数开始运行(初始函数),一旦这个函数运行完毕,线程也结束运行。
  • 整个进程是否执行完毕的标志是:主线程是否执行完,如果主线程执行完毕了,就代表整个进程执行完毕了,此时如果其他子线程还没有执行完,也会被强行终止。所以,一般情况下,如果想保持子线程的运行状态的话,那么大家要让主线程一直保持运行,不要让主线程运行完毕。【此条有例外,以后会解释】

1.1 范例

创建一个线程:

  1. 包含头文件thread -> #include <thread>
  2. 写初始函数 -> void myprint() {}
  3. 在main中创建thread -> thread mytobj(myprint);
 #include <iostream>
#include <thread>
using namespace std;

void myprint()
{
	cout << "我的线程开始执行了" << endl;
	/*
	......
	*/
	cout << "我的线程执行完毕了" << endl;
}

//必须明确一点:这个程序有两个线程在跑。相当于整个程序的执行有两条线在同时走,即使一条线被堵住了,另外一条线还是可以通行的,这就是多线程。
int main()
{
	thread mytobj(myprint);
	mytobj.join();

	cout << "主线程收尾,最终主线程安全正常退出!" << endl;

	system("pause");
	return 0;
}

1.2 thread

thread:是个标准库的类!

void myprint() {}

int main()
{
	//创建一个线程对象,myprint为可调用对象
	/*这行代码含义:Ⅰ.创建了线程,线程执行起点(入口)是myprint这个函数(可调用对象);
			      Ⅱ.myprint线程开始执行 */
	thread mytobj(myprint);
}

1.3 join()

join():加入/汇合,说白了就是阻塞,阻塞主线程,让主线程等待子线程执行完毕,然后子线程和主线程汇合,然后主线程再往下走。

void myprint() {}

int main()
{
    thread mytobj(myprint);
    
    mytobj.join();//主线程阻塞到这里等待myprint()执行完,当子线程执行完毕,这个join()就执行完毕,主线程就继续往下走
    
    cout << "主线程收尾,最终主线程安全正常退出!" << endl;
    
    return 0;
}

注意:如果主线程执行完毕了,但子线程没执行完毕且不能继续执行,这种程序是不合格的!

一个书写良好的程序,大部分情况下应该是主线程等待子线程执行完毕后,自己才能最终退出!

1.4 detach()

detach():分离,主线程不再和子线程汇合,主线程和子线程各自执行,主线程也不必等待子线程运行结束,主线程可以先执行结束,这并不影响子线程的执行。

为什么引入detach() ?

**答:**当创建了很多子线程时,让主线程逐个等待子线程结束,这种编程方法不太好,所以引入了detach()

  • 一旦引入detach()之后,与这个主线程关联的thread对象就会失去与这个主线程的关联,此时这个子线程就会驻留后台运行(此时主线程与子线程已经失去联系),这个子线程就会被C++运行时库接管,当这个子线程执行完成后,由运行时库负责清理该线程相关的资源(守护线程)
void myprint() {}

int main()
{
    thread mytobj(myprint);
    
    mytobj.detach();
    
    cout << "I love China!" << endl;
    
    return 0;
}
  • detach()使线程myprint失去我们的控制。

注意:一旦调用了detach(),就不能再使用join(),否则系统会报告异常。

1.5 joinable()

joinable():判断是否可以成功使用join()或者detach(),可以使用join()或者detach(),则返回 true,否则,返回 false。

#include <iostream>
#include <thread>
using namespace std;

void myprint() {}
int main()
{
	thread mytobj(myprint);

	if (mytobj.joinable())
	{
		cout << "1.joinable() == true" << endl;		//√
	}
	else
	{
		cout << "1.joinable() == false" << endl;
	}

	mytobj.detach();

	if (mytobj.joinable())
	{
		cout << "2.joinable() == true" << endl;
	}
	else
	{
		cout << "2.joinable() == false" << endl;	//√
	}

	cout << "I love China" << endl;

	return 0;
}

2、其他创建线程的手法

2.1 、用类对象

  • 用类对象创建线程时,类内必须要重载(),使类对象成为可调用对象。
#include <iostream>
#include <thread>
using namespace std;

class TA
{
public:
	void operator()()		//不能带参数
	{
		cout << "我的线程operator()开始执行了" << endl;
		/*
		......
		*/
		cout << "我的线程operator()执行完毕了" << endl;
	}
};

int main()
{
	TA ta;
	thread mytobj(ta); 	 //ta:可调用对象

	mytobj.join();		//等待子线程执行结束

	cout << "I love China 1" << endl;
	cout << "I love China 2" << endl;
	cout << "I love China 3" << endl;
	cout << "I love China 4" << endl;
	cout << "I love China 5" << endl;

	system("pause");
	return 0;
}

当使用detach()时,要注意,类内创建类变量时不能使用变量的引用,当在主线程中创建变量并赋给类内变量时,传递的是主线程中变量的内存地址,当主线程结束后,主线程中变量的内存就会被释放,主线程中变量也不复存在,这样的话,子线程访问到的内存地址可能会被其他程序所占领,所指向的值也可能会被修改,这样子线程就会出现不可预料的结果,所以应使用值传递。

  • 如下案例:

    #include <iostream>
    #include <thread>
    using namespace std;
    
    class TA
    {
    public:
        //int& m_i;
        int m_i;	//正确用法,使用值传递
    
    	//TA(int& i) :m_i(i) {}
        TA(int i) :m_i(i) {}	 //正确用法,使用值传递
        
        TA(const TA& ta) :m_i(ta.m_i)
    	{
    		cout << "TA()拷贝构造函数被执行" << endl;
    	}
    
    	~TA()
    	{
    		cout << "~TA()析构函数被执行" << endl;
    	}
        
    	void operator()()		//不能带参数
    	{
    		cout << "m_i 1 的值为:" << m_i << endl;
    		cout << "m_i 2 的值为:" << m_i << endl;
    		cout << "m_i 3 的值为:" << m_i << endl;
    		cout << "m_i 4 的值为:" << m_i << endl;
    		cout << "m_i 5 的值为:" << m_i << endl;
    	}
    };
    
    int main()
    {
    	TA ta;
    	thread mytobj(ta); //ta:可调用对象
    
        mytobj.join();
    	//mytobj.detach();
    
    	cout << "I love China 1" << endl;
    	cout << "I love China 2" << endl;
    	cout << "I love China 3" << endl;
    	cout << "I love China 4" << endl;
    	cout << "I love China 5" << endl;
    
    	system("pause");
    	return 0;
    }
    

问题:一旦调用了detach(),当主线程结束了,子线程用的 ta(类对象)这个对象还在吗?

**答:**ta 这个对象确实不在了,但是创建子线程时,这个对象实际上是被复制到子线程中去的,执行完主线程后,ta会被销毁,但是所复制的TA对象依旧存在。

  • 使用join()来证明以上问题,程序执行结果如下:

    image-20211029161900043

2.2、 用lambda表达式

#include <iostream>
#include <thread>
using namespace std;

int main()
{
    auto mylamthread = []
	{
		cout << "我的线程开始执行了" << endl;
		/*
		......
		*/
		cout << "我的线程执行结束了" << endl;
	};

	thread mytobj(mylamthread);
	mytobj.join();
	//mytobj.detach();

	cout << "I love China 1" << endl;
	cout << "I love China 2" << endl;
	cout << "I love China 3" << endl;
	cout << "I love China 4" << endl;
	cout << "I love China 5" << endl;

	return 0;
}

注:本人学习c++多线程视频地址:C++多线程学习地址

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值