C++11 - 使用std::thread::join()/std::thread::detach()方法需要注意的点

1 调用std::thread::join()方法等待线程退出时的示例问题程序

#include <iostream>
#include "conio.h"

#include <memory>
#include <thread>
#include <atomic>


class BaseThread
{
public:
	typedef std::shared_ptr<BaseThread> ptr;
	BaseThread()
	{
		m_Age = 10;
		m_ChildThreadPtr = nullptr;
		m_bChildStop = false;
	}

	virtual~BaseThread()
	{
		std::cout << "析构" << std::endl;
	}
	
	void ChildThreadFunc()
	{
		while (!m_bChildStop)
		{
			m_Age++;
			std::cout << "子线程运行" << std::endl;
			std::this_thread::sleep_for(std::chrono::microseconds(3000000));
		}
	}

	void StartThread()
	{
		if (m_ChildThreadPtr != nullptr)
			m_ChildThreadPtr.reset();
		m_ChildThreadPtr = std::make_shared<std::thread>(&BaseThread::ChildThreadFunc, this);
	}

	void StopThread()
	{
		m_bChildStop = true;

		if (m_ChildThreadPtr != nullptr)
		{
			m_ChildThreadPtr->join();
			//if (m_ChildThreadPtr->joinable())
			//{
			//	m_ChildThreadPtr->join();
			//}
		}
	}

private:
	std::atomic<int> m_Age;
	std::shared_ptr<std::thread> m_ChildThreadPtr;
	std::atomic<bool> m_bChildStop;
};


int main()
{
	std::shared_ptr<BaseThread> pBaseThread = std::make_shared<BaseThread>();

	if (pBaseThread != nullptr)
	{
		pBaseThread->StartThread();

		while (true)
		{
			// 在此处填入需要循环的代码

			if (_kbhit()) // 如果有按键被按下
			{
				if (_getch() == 'q') //如果按下了q键则跳出循环
				{
					std::cout << "退出" << std::endl;

					// 不小心执行了两次停止线程
					pBaseThread->StopThread();
				}

			}
		}
	}

	getchar();

	return 0;
}


在上述程序中,我们声明了一个BaseThread类,在该类中我们使用类的成员函数ChildThreadFunc()作为子线程函数,并使用StartThread()开启执行子线程,StopThread()停止执行子线程。

在main()函数中我们先使用StartThread()开启执行子线程,然后再通过按q键调用StopThread()退出子线程。

但是我们一不小心就连续按了两下q键,导致调用了两次StopThread()方法,这个时候程序出现了crash,并出现了如下报错:
在这里插入图片描述

2 问题原因

在上述main函数中,当我们按下了第一次q键的时候,子程序已经调用了join方法,这导致std::thread对象失去了与之相关联的线程对象,所以当我们再按下了一次q键,发现现在的std::thread对象已经不可join,导致了程序发生了中断。

3 std::thread出现不可join的几种情况

std::thread在以下几种情况下是不可join的:

  • 由std::thread默认构造的std::thread对象,也就是没有指定线程函数的std::thread对象是不可join的;
  • 该std::thread对象被std::move操作
  • 该std::thread对象已经执行过std::thread::join()方法或者std::thread::detach()方法,此时std::thread对象是不可join的;

4 使用std::thread::join()/std::thread::detach()方法需要注意的点

4.1 在执行std::thread::join()/std::thread::detach()方法之前最好判断该std::thread对象是可join的

if (m_ChildThreadPtr->joinable())
{
	m_ChildThreadPtr->join();
}

即第1节中的bug可以如此修复:

#include <iostream>
#include "conio.h"

#include <memory>
#include <thread>
#include <atomic>


class BaseThread
{
public:
	typedef std::shared_ptr<BaseThread> ptr;
	BaseThread()
	{
		m_Age = 10;
		m_ChildThreadPtr = nullptr;
		m_bChildStop = false;
	}

	virtual~BaseThread()
	{
		std::cout << "析构" << std::endl;
	}
	
	void ChildThreadFunc()
	{
		while (!m_bChildStop)
		{
			m_Age++;
			std::cout << "子线程运行" << std::endl;
			std::this_thread::sleep_for(std::chrono::microseconds(3000000));
		}
	}

	void StartThread()
	{
		if (m_ChildThreadPtr != nullptr)
			m_ChildThreadPtr.reset();
		m_ChildThreadPtr = std::make_shared<std::thread>(&BaseThread::ChildThreadFunc, this);
	}

	void StopThread()
	{
		m_bChildStop = true;

		if (m_ChildThreadPtr != nullptr)
		{
			if (m_ChildThreadPtr->joinable())
			{
				m_ChildThreadPtr->join();
			}
		}
	}

private:
	std::atomic<int> m_Age;
	std::shared_ptr<std::thread> m_ChildThreadPtr;
	std::atomic<bool> m_bChildStop;
};


int main()
{
	std::shared_ptr<BaseThread> pBaseThread = std::make_shared<BaseThread>();

	if (pBaseThread != nullptr)
	{
		pBaseThread->StartThread();

		while (true)
		{
			// 在此处填入需要循环的代码

			if (_kbhit()) // 如果有按键被按下
			{
				if (_getch() == 'q') //如果按下了q键则跳出循环
				{
					std::cout << "退出" << std::endl;

					pBaseThread->StopThread();
				}

			}
		}
	}

	getchar();

	return 0;
}

在调用join之前判断joinable,只有在线程对象是可join的情况下再进行join或者detach操作。

4.2 不要忘记对有关联的std::thread对象调用join或者detach方法

这里有关联的指的是已经为该线程指定了线程函数。

例如我们将第一节的程序进行修改,删除按q键退出子程序的代码。

#include <iostream>
#include "conio.h"

#include <memory>
#include <thread>
#include <atomic>


class BaseThread
{
public:
	typedef std::shared_ptr<BaseThread> ptr;
	BaseThread()
	{
		m_Age = 10;
		m_ChildThreadPtr = nullptr;
		m_bChildStop = false;
	}

	virtual~BaseThread()
	{
		std::cout << "析构" << std::endl;
	}
	
	void ChildThreadFunc()
	{
		while (!m_bChildStop)
		{
			m_Age++;
			std::cout << "子线程运行" << std::endl;
			std::this_thread::sleep_for(std::chrono::microseconds(3000000));
		}
	}

	void StartThread()
	{
		if (m_ChildThreadPtr != nullptr)
			m_ChildThreadPtr.reset();
		m_ChildThreadPtr = std::make_shared<std::thread>(&BaseThread::ChildThreadFunc, this);
	}

	void StopThread()
	{
		m_bChildStop = true;

		if (m_ChildThreadPtr != nullptr)
		{
			if (m_ChildThreadPtr->joinable())
			{
				m_ChildThreadPtr->join();
			}
		}
	}

private:
	std::atomic<int> m_Age;
	std::shared_ptr<std::thread> m_ChildThreadPtr;
	std::atomic<bool> m_bChildStop;
};


int main()
{
	std::shared_ptr<BaseThread> pBaseThread = std::make_shared<BaseThread>();

	if (pBaseThread != nullptr)
	{
		pBaseThread->StartThread();
	}

	getchar();

	return 0;
}

运行上述程序,程序报错:
在这里插入图片描述

在一个有关联的线程函数的std::thread对象如果没有调用join或者detach方法会在std::thread对象析构的时候中断程序。如果程序发生异常,也必须在异常处理中调用std::thread对象的join或者detach方法。

如果有兴趣,可以访问我的个站:https://www.stubbornhuang.com,获取更多感兴趣的知识。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

HW140701

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值