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

来源:互联网 发布:淘宝店招设计方法 编辑:程序博客网 时间:2024/06/16 08:16

在看书时,了解到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,而应采用线程锁或其他线程切换方法。

1 0
原创粉丝点击