Boost编程之--慎用线程的this_thread::yield()方法

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/renstarone/article/details/19922091

在看书时,了解到boost线程中的yield方法:可以将本线程的CPU时间片放弃,并允许其他线程运行。认为其是一个操作线程之利器,所以写了个3个线程,循环打印ABC字符串,以验证其交出时间片功能。

代码如下:

#include <windows.h>
#include <iostream>
#include <boost/thread.hpp>
#include <boost/atomic.hpp>
#include <boost/ref.hpp>

using namespace std;
using namespace boost;

enum MARK
{
	A,
	B,
	C,
};

mutex io_mutex;
typedef boost::atomic<MARK> ENUM_MARK;

void print_abc(ENUM_MARK& mark, MARK CurID)
{
	for (int nIndex = 0; nIndex < 10;)
	{
		mutex::scoped_lock lock(io_mutex);
		switch (CurID)
		{
		case A:
			{
				if (mark == MARK::C)
				{
					cout << "A";
					mark = MARK::A;
					nIndex++;
				}
				break;
			}
		case B:
			{
				if (mark == MARK::A)
				{
					cout << "B";
					mark = MARK::B;
					nIndex++;
				}
				break;
			}
		case C:
			{
				if (mark == MARK::B)
				{
					cout << "C" << endl;
					mark = MARK::C;
					nIndex++;
				}
				break;
			}
		default:
			break;
		}

		// 加个yield,交出本线程时间片,让其他线程运行。
		this_thread::yield();
	}
}

int main()
{
	ENUM_MARK mark	= MARK::C;
	int nRetry		= 0;

	// 创建3个线程,依次输出ABC。
	// 连续循环3此,观察运行时间。
	while (nRetry < 3)
	{
		DWORD nStart = ::GetTickCount();

		thread t1(print_abc, boost::ref(mark), MARK::A);
		thread t2(print_abc, boost::ref(mark), MARK::B);
		thread t3(print_abc, boost::ref(mark), MARK::C);

		// 等待t3线程结束,因为其输出最后一个C。
		t3.join();

		DWORD nEnd		= ::GetTickCount();
		DWORD nTotal	= nEnd - nStart;

		cout << "Total times:" << nTotal << endl;
		nRetry++;
	}

	getchar();
	return 0;
}

运行后,3个线程打印10次ABC所耗的时间大约是30ms。发现30ms有点长,是不是yield引起的?


当我把this_thread::yield();注释掉后,再次运行,发现线程运行速度加快,平均10ms不到。


比较奇怪,查看yield的实现代码,才发现其实它就执行了Sleep(0),Sleep(0)的确会放弃CPU时间片,允许其他线程运行。但其它线程,也包含了放弃CPU时间片的线程,这样就可能造成单个线程无限次的放弃CPU时间片,又再一次获得运行权限。

this_thread::yield()的代码定义如下:

void yield() BOOST_NOEXCEPT
        {
            detail::win32::Sleep(0);
        }

为了更好的验证一下自己的推论,我直接使用了Win32 API的线程代码:

#include <windows.h>
#include <iostream>
#include <string>

using namespace std;

CRITICAL_SECTION CK;

enum MARK
{
	A,
	B,
	C,
};

struct MyStruct
{
	volatile MARK* mark;
	MARK CurID;
};

DWORD WINAPI Win32_Thread(LPVOID pStruct)
{
	MyStruct* myStruct = (MyStruct*)(pStruct);

	for (int nIndex = 0; nIndex < 10;)
	{
		EnterCriticalSection(&CK);

		switch (myStruct->CurID)
		{
		case A:
			{
				if (*myStruct->mark == MARK::C)
				{
					cout << "A";
					*myStruct->mark = MARK::A;
					nIndex++;
				}
				break;
			}
		case B:
			{
				if (*myStruct->mark == MARK::A)
				{
					cout << "B";
					*myStruct->mark = MARK::B;
					nIndex++;
				}
				break;
			}
		case C:
			{
				if (*myStruct->mark == MARK::B)
				{
					cout << "C" << endl;
					*myStruct->mark = MARK::C;
					nIndex++;
				}
				break;
			}
		default:
			break;
		}

		LeaveCriticalSection(&CK);
	}
	return 0;
}

int main()
{
	::InitializeCriticalSection(&CK);

	MyStruct myStruct;
	myStruct.CurID	= A;
	myStruct.mark	= new MARK;
	*myStruct.mark	= C;

	DWORD dwID1, dwID2, dwID3;
	HANDLE hThreadA = ::CreateThread(NULL, 0, &Win32_Thread, &myStruct, 0, &dwID1);

	MyStruct myStruct2;
	myStruct2.CurID	= B;
	myStruct2.mark	= myStruct.mark;
	HANDLE hThreadB = ::CreateThread(NULL, 0, &Win32_Thread, &myStruct2, 0, &dwID2);

	MyStruct myStruct3;
	myStruct3.CurID	= C;
	myStruct3.mark	= myStruct.mark;
	HANDLE hThreadC = ::CreateThread(NULL, 0, &Win32_Thread, &myStruct3, 0, &dwID3);

	getchar();
	DeleteCriticalSection(&CK);

	return 0;
}

像上面这样,没有用Sleep(0),3个线程都快速的输出了ABC。但如果我加了个Sleep(1)在线程的LeaveCriticalSection(&CK);前面,就发生了恶性竞争,基本上是每隔几秒钟,才输出ABC中的一个字母。


以下是出现恶性竞争的代码:

#include <windows.h>
#include <iostream>
#include <string>

using namespace std;

CRITICAL_SECTION CK;

enum MARK
{
	A,
	B,
	C,
};

struct MyStruct
{
	volatile MARK* mark;
	MARK CurID;
};

DWORD WINAPI Win32_Thread(LPVOID pStruct)
{
	MyStruct* myStruct = (MyStruct*)(pStruct);

	for (int nIndex = 0; nIndex < 10;)
	{
		EnterCriticalSection(&CK);

		switch (myStruct->CurID)
		{
		case A:
			{
				if (*myStruct->mark == MARK::C)
				{
					cout << "A";
					*myStruct->mark = MARK::A;
					nIndex++;
				}
				break;
			}
		case B:
			{
				if (*myStruct->mark == MARK::A)
				{
					cout << "B";
					*myStruct->mark = MARK::B;
					nIndex++;
				}
				break;
			}
		case C:
			{
				if (*myStruct->mark == MARK::B)
				{
					cout << "C" << endl;
					*myStruct->mark = MARK::C;
					nIndex++;
				}
				break;
			}
		default:
			break;
		}
		// 这里加了Sleep,引起恶性竞争。
		::Sleep(1);
		LeaveCriticalSection(&CK);
	}
	return 0;
}

int main()
{
	::InitializeCriticalSection(&CK);

	MyStruct myStruct;
	myStruct.CurID	= A;
	myStruct.mark	= new MARK;
	*myStruct.mark	= C;

	DWORD dwID1, dwID2, dwID3;
	HANDLE hThreadA = ::CreateThread(NULL, 0, &Win32_Thread, &myStruct, 0, &dwID1);

	MyStruct myStruct2;
	myStruct2.CurID	= B;
	myStruct2.mark	= myStruct.mark;
	HANDLE hThreadB = ::CreateThread(NULL, 0, &Win32_Thread, &myStruct2, 0, &dwID2);

	MyStruct myStruct3;
	myStruct3.CurID	= C;
	myStruct3.mark	= myStruct.mark;
	HANDLE hThreadC = ::CreateThread(NULL, 0, &Win32_Thread, &myStruct3, 0, &dwID3);

	getchar();
	DeleteCriticalSection(&CK);

	return 0;
}

从而我们得出以下结论:

1. yield方法其实就是::Sleep(0)。

2. Sleep会交出CPU时间片,允许其他线程运行,但“其他线程”也包含了交出CPU时间片的那个线程。

3. 想要更好的进行线程切换,不能够使用Sleep,而应采用线程锁或其他线程切换方法。

展开阅读全文

没有更多推荐了,返回首页