在看书时,了解到boost线程中的yield方法:可以将本线程的CPU时间片放弃,并允许其他线程运行。认为其是一个操作线程之利器,所以写了个3个线程,循环打印ABC字符串,以验证其交出时间片功能。
代码如下:
#include
#include
#include
#include
#include
using namespace std;
using namespace boost;
enum MARK
{
A,
B,
C,
};
mutex io_mutex;
typedef boost::atomic 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;
}
当我把this_thread::yield();注释掉后,再次运行,发现线程运行速度加快,平均10ms不到。
比较奇怪,查看yield的实现代码,才发现其实它就执行了Sleep(0),Sleep(0)的确会放弃CPU时间片,允许其他线程运行。但其它线程,也包含了放弃CPU时间片的线程,这样就可能造成单个线程无限次的放弃CPU时间片,又再一次获得运行权限。
this_thread::yield()的代码定义如下:
void yield() BOOST_NOEXCEPT
{
detail::win32::Sleep(0);
}
#include
#include
#include
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;
}
以下是出现恶性竞争的代码:
#include
#include
#include
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,而应采用线程锁或其他线程切换方法。