1. 程式人生 > >Boost程式設計之--慎用執行緒的this_thread::yield()方法

Boost程式設計之--慎用執行緒的this_thread::yield()方法

在看書時,瞭解到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,而應採用執行緒鎖或其他執行緒切換方法。

相關推薦

Boost程式設計--慎用執行this_thread::yield()方法

在看書時,瞭解到boost執行緒中的yield方法:可以將本執行緒的CPU時間片放棄,並允許其他執行緒執行。認為其是一個操作執行緒之利器,所以寫了個3個執行緒,迴圈列印ABC字串,以驗證其交出時間片功

併發程式設計執行執行安全

什麼是執行緒安全? 為什麼有執行緒安全問題? 當多個執行緒同時共享,同一個全域性變數或靜態變數,做寫的操作時,可能會發生資料衝突問題,也就是執行緒安全問題。但是做讀操作是不會發生資料衝突問題。 案例: 需求現在有100張火車票,有兩個視窗同時搶火車票,請使用多執行緒模擬搶票效果。 p

併發程式設計執行基礎

執行緒與程序區別 每個正在系統上執行的程式都是一個程序。每個程序包含一到多個執行緒。執行緒是一組指令的集合,或者是程式的特殊段,它可以在程式裡獨立執行。也可以把它理解為程式碼執行的上下文。所以執行緒基本上是輕量級的程序,它負責在單個程式裡執行多工。通常由作業系統負責多個執行緒的排程和執行。

Python併發程式設計執行使用

目錄 一 開啟執行緒的兩種方式 二 在一個程序下開啟多個執行緒與在一個程序下開啟多個子程序的區別 三 練習 四 執行緒相關的其他方法 五 守護執行緒 六 Python GIL(Global Interpreter Lock) 七 同步鎖 八 死鎖現象

Python3 與 C# 併發程式設計執行上篇

2.2.加強篇¶ 其實以前的Linux中是沒有執行緒這個概念的,Windows程式設計師經常使用執行緒,這一看~方便啊,然後可能是當時程式設計師偷懶了,就把程序模組改了改(這就是為什麼之前說Linux下的多程序程式設計其實沒有Win下那麼“重量級”),弄了個精簡版程序==>執行緒(核心是分不出程序

Java併發程式設計 HashMap執行不安全

我想在平時的多執行緒程式設計中,容器的使用是很普遍的,但是你有沒有考慮過有些容器是不安全的,如Haspmap、ArrayList。這裡講解一下Hashmap不安去體現在哪裡。 插入時不安全: 如果有兩個執行緒A和B,都進行插入資料,剛好經過雜湊計算後得到的雜湊碼是一樣的,即插入的

Java網路程式設計4.UDP網路程式設計執行優化

UDP網路程式設計之多執行緒優化——DatagramSocket類 1、UDP網路程式設計之多執行緒優化的思想 (1)一個執行緒實現客戶端——傳送資料 (2)一個執行緒實現伺服器端——接收資料

併發程式設計執行基礎-Thread和Runnable的區別及聯絡(二)

上篇文章講述了建立執行緒的常用方式 本篇主要分析一下Thread和Runnable兩種方式建立執行緒的區別及聯絡 聯絡: ▶Thread類實現了Runable介面。 ▶都需要重寫裡面Run方法。 區別: ▶Thread方式不支援多繼承,Runnable方式支援多個實現 ▶Runnable更容易實

併發程式設計執行基礎-執行五種狀態(三)

原文地址:https://www.cnblogs.com/wangyichuan/p/5990821.html 執行緒從建立、執行到結束總是處於下面五個狀態之一:新建狀態、就緒狀態、執行狀態、阻塞狀態及死亡狀態。     1.新建狀態(New):         當用new操作符建立一個執行緒時,

Python學習【第24篇】:死鎖,遞迴鎖,訊號量,Event事件,執行Queue python併發程式設計執行2------------死鎖與遞迴鎖,訊號量等

python併發程式設計之多執行緒2------------死鎖與遞迴鎖,訊號量等 一、死鎖現象與遞迴鎖 程序也是有死鎖的 所謂死鎖: 是指兩個或兩個以上

Python學習【第23篇】:利用threading模組開執行 python併發程式設計執行1

python併發程式設計之多執行緒1 一多執行緒的概念介紹 threading模組介紹 threading模組和multiprocessing模組在使用層

Java併發程式設計深入執行池原理及實現

Java執行緒池在實際的應用開發中十分廣泛。雖然Java1.5之後在JUC包中提供了內建執行緒池可以拿來就用,但是這之前仍有許多老的應用和系統是需要程式設計師自己開發的。因此,基於執行緒池的需求背景、技術要求瞭解執行緒池原理和實現,一方面可以更為深刻理解Java多執行緒開發,有助於解決業務系統中因為執行緒問題

併發程式設計執行

一、什麼是執行緒 在傳統作業系統中,每個程序有一個地址空間,而且預設就有一個控制執行緒 執行緒顧名思義,就是一條流水線工作的過程,一條流水線必須屬於一個車間,一個車間的工作過程是一個程序 車間負責把資源整合到一起,是一個資源單位,而一個車間內至少有一個流水線  流水線的工作需要電源,電源就相當於cpu 所以,

PHP 高階程式設計執行-訊息佇列

1. 多執行緒環境安裝 1.1. PHP 5.5.9 安裝PHP 5.5.9 https://github.com/oscm/shell/blob/master/php/5.5.9.sh ./configure --prefix=/srv/php-5.5.9 \ --wi

自圓其說併發程式設計——認識執行開銷

本系列博文,自圓其說之——聊聊併發,是希望自己在學習併發的過程中,建立起一套屬於自己的見解和知識體系,並能對其中的各種姿勢能自圓其說,做到知其言,並知其所以言。 首先,我們要學習併發程式設計的目的,是為了要讓程式跑得更加的快,但是在併發中,我們常常會面

(十二)boost執行高階特性

很多時候,執行緒不僅僅是執行一些耗時操作,可能我們還需要得到執行緒的返回值,一般的處理方法就是定義一個全域性狀態變數,不斷輪訓狀態,就如我目前維護的一個專案,全域性變數定義了N中狀態,看的讓人抓狂。該專案的大體邏輯是這樣的,啟動K個執行緒,當執行緒執行到某一個點時,進行輪

python併發程式設計執行理論部分

一 什麼是執行緒     在傳統作業系統中,每個程序有一個地址空間,而且預設就有一個控制執行緒   執行緒顧名思義,就是一條流水線工作的過程,一條流水線必須屬於一個車間,一個車間的工作過程是一個程序       車間負責把資源整合到一起,是一個資源單位,而一個車間內至少有一個流水線       流水線

socket網路程式設計執行阻塞IO例項

先介紹一下網路層次結構、socket與TCP/UDP之間的關係。同步、非同步,阻塞、非阻塞的區別。 網路由下往上分為物理層、資料鏈路層、網路層、傳輸層、會話層、表示層和應用層。 IP 協議對應於網路層,TCP協議對應於傳輸層,HTTP協議對應於應用層,三者從本質上來說沒有可

Java網路程式設計執行Client-Server

前面廢話過了,現在就直接看程式碼吧! ThreadedClient.java package exercise01; import java.io.*; import java.net.*; public class ThreadedClient { privat

C#程式設計執行爬蟲程式

一、簡單介紹: 技術方面主要包括: (1)技術選型: 1) 課程設計使用的開發語言是C#。 2)課程設計選用了檔案流方式獲取網站資料。 3)課程設計使用多執行緒抓取網頁程式碼。 4)課