1. 程式人生 > >Windows下執行緒的同步

Windows下執行緒的同步

核心物件

首先介紹核心物件的概念。應用程式在執行過程中會建立各種資源,如程序,執行緒,檔案,互斥量等等。這些資源均由作業系統管理。作業系統管理這些資源的方式就是:記錄這些資源的相關資訊,會在其內部生成資料塊。每種資源需要維護的資訊不同,因此每種資源擁有的資料塊格式也不同。這類資料塊就是“核心物件”。

即使資源的建立請求是在程序內部完成的,但核心物件的所有者並不是該程序,而是核心即作業系統。核心物件的建立,管理,銷燬等都是由作業系統完成。

執行緒的建立

Windows下建立執行緒有兩種方法,函式的原型如下:

uintptr_t _beginthreadex(void *security, unsigned stack_size, unsigned(*start_address)(void *), void *arglist, unsigned initflag, unsigned *thrdaddr);

HANDLE CreateThread(LPSECURITY_ATTRIBUTES lpThreadAttributes, SIZE_T dwStackSize, LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter, DWORD dwCreationFlags, LPDWORD lpThreadId);  

這裡只介紹_beginthreadex,因為CreateThread函式調用出的執行緒在使用C/C++標準函式時並不穩定。

_beginthreadex函式的引數含義:

security:執行緒安全相關資訊,使用預設設定時傳遞NULL。

stack_size:要分配給執行緒的棧大小,傳遞0時生成預設大小的棧。

start_address:傳遞給執行緒的main函式資訊。

arglist:執行緒main函式的引數資訊。

initflag:用於指定執行緒建立後的行為,傳遞0時,執行緒建立後立即進入可執行狀態。

thrdaddr:用於儲存執行緒ID的變數地址值。

函式呼叫成功時返回執行緒控制代碼,失敗時返回0。

示例:

#include<iostream>
#include<cstdio>
#include<Windows.h>
#include<process.h>
using namespace std;

unsigned WINAPI threadFunc(void *arg);
//WINAPI是Windows固有的關鍵字,它用於指定引數傳遞方向,分配的棧返回方式等函式呼叫規定。
//插入它是為了遵守_beginthreadex函式要求的呼叫規定。

int main()
{
	HANDLE hPthread;
	unsigned pthreadId;
	int param(5);

	hPthread = (HANDLE)_beginthreadex(NULL, 0, threadFunc, static_cast<void *>(¶m), 0, &pthreadId);

	if (hPthread == 0)
	{
		cerr << "_beginthreadex() error!\n";
		return -1;
	}

	Sleep(3000);
	cout << "end of main.\n";

	return 0;
}

unsigned WINAPI threadFunc(void *arg)
{
	int param(*(static_cast<int *>(arg)));
	cout << param << endl;

	return 0;
}

執行緒控制代碼和ID的區別:控制代碼在不同程序中可以重複,ID即使在不同程序中也不能重複。因此,執行緒ID是在作業系統範圍內區分所有執行緒,執行緒控制代碼是在程序內區分該程序擁有的執行緒。

核心物件的2中狀態

核心物件的兩種狀態:

(1)終止狀態,又稱signaled狀態。

(2)未終止狀態,又稱non-signaled狀態。

Windows提供兩個函式WaitForSingleObject和WaitForMultipleObjects,它們的作用是等待引數中指定的物件進入signaled狀態。函式原型如下:

DWORD WaitForSingleObject(HANDLE hHandle, DWORD dwMilliseconds);

引數含義:

hHandle:核心物件控制代碼。

dwMilliseconds:以ms為單位設定超時時間,INFINITE代表無限等待。

物件進入signaled時返回WAIT_OBJECT_0,超時時返回WAIT_TIMEOUT。

DWORD WaitForMultipleObjects(DWORD nCount, HANDLE *lpHandles, BOOL bWaitAll, DWORD dwMilliseconds);

引數含義:

nCount:需驗證的核心物件數。

lpHandles:存有核心物件控制代碼的陣列地址值。

bWaitAll:TRUE表示當全部核心物件都變成signaled時返回,FALSE表示只要有一個核心物件程式設計signaled就返回。

dwMilliseconds:同理。

成功時返回事件資訊,失敗時返回WAIT_FAILED。

WaitForSingleObject在很多情況下都會用到,如互斥量的上鎖等都是呼叫這個函式,程序阻塞等待執行緒結束也可以呼叫這個函式,因為執行緒終止時,執行緒核心物件也會進入signaled狀態。值得注意的是,有時候WaitForSingleObject在函式返回後,作為引數的核心物件會重置為non-signaled狀態(仔細想想,這正是WaitForSingleObject能用來當作互斥量上鎖的原因),重不重置取決於傳遞過來的核心物件。

函式返回時會再次變成non-signaled狀態的核心物件稱為“auto-reset模式”的核心物件,否則稱作“manual-reset模式”的核心物件。互斥量就屬於auto-reset模式。

示例:

#include<iostream>
#include<cstdio>
#include<Windows.h>
#include<process.h>
using namespace std;

unsigned WINAPI threadFunc(void *arg);

int main()
{
	HANDLE hThread;
	DWORD res;
	unsigned threadId;
	int param(5);

	hThread = (HANDLE)_beginthreadex(NULL, 0, threadFunc, static_cast<void *>(¶m), 0, &threadId);
	if (hThread == 0)
	{
		cout << "_beginthreadex() error!\n";
		return -1;
	}

	if ((res = WaitForSingleObject(hThread, INFINITE)) == WAIT_FAILED)     //等待執行緒終止
	{
		cout << "thread wait error!\n";
		return -1;
	}

	if (res == WAIT_OBJECT_0) cout << "wait result:signaled.\n";
	else cout << "wait result:time-out.\n";

	cout << "end of main.\n";

	return 0;
}

unsigned WINAPI threadFunc(void *arg)
{
	int cnt(*(static_cast<int *>(arg)));
	
	for (int i = 0; i < cnt; i++)
	{
		Sleep(1000);
		cout << "running thread.\n";
	}

	return 0;
}

使用者態和核心態

在Windows作業系統下,程序執行的狀態有兩種:使用者態和核心態。

使用者態下,會禁止訪問物理裝置,而且會限制訪問的記憶體區域。核心態下,並不會有這兩種限制。

一個程序的執行過程中,往往少不了這兩種狀態間的切換。例如,當一個程序呼叫函式建立一個執行緒時,建立申請是在使用者態中進行的。但對於執行緒核心物件是由作業系統建立和管理的,所以程序會從使用者態切換到核心態,然後進行執行緒的建立。總的來說當在使用者態遇到系統呼叫或者相應中斷時等等,都會切換到核心態。

使用者態下的同步方法

使用者態下的同步是無需作業系統幫助而在應用程式級別進行的同步方法。因為無需切換到核心態,因此速度會相對快一些,當然伴隨的是功能會少一些。

該方法是使用CRITICAL_SECTION(cs)物件,這不是核心物件,這個物件擁有和互斥量差不多的功能。

cs的初始化和銷燬函式:

void InitializeCriticalSection(LPCRITICAL_SECTION lpCriticalSection);
void DeleteCriticalSection(LPCRITICAL_SECTION lpCriticalSection);

引數是cs物件變數的地址值。

cs的上鎖和解鎖函式:

void EnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection);
void LeaveCriticalSection(LPCRITICAL_SECTION lpCriticalSection);

示例:

#include<iostream>
#include<cstdio>
#include<Windows.h>
#include<process.h>
using namespace std;
const int NUMTHREAD = 50;
typedef long long ll;

ll num;
CRITICAL_SECTION cs;

unsigned WINAPI threadInc(void *arg);
unsigned WINAPI threadDec(void *arg);

int main()
{
	HANDLE hHandles[NUMTHREAD];

	InitializeCriticalSection(&cs);

	for (int i = 0; i < NUMTHREAD; i++)
	{
		if (i & 1) hHandles[i] = (HANDLE)_beginthreadex(NULL, 0, threadInc, NULL, 0, NULL);
		else hHandles[i] = (HANDLE)_beginthreadex(NULL, 0, threadDec, NULL, 0, NULL);
	}
	WaitForMultipleObjects(NUMTHREAD, hHandles, TRUE, INFINITE);

	DeleteCriticalSection(&cs);

	cout << "Result:" << num << endl;

	return 0;
}

unsigned WINAPI threadInc(void *arg)
{
	EnterCriticalSection(&cs);

	for (int i = 0; i < 5000; i++)
		num += 1;

	LeaveCriticalSection(&cs);

	return 0;
}

unsigned WINAPI threadDec(void *arg)
{
	EnterCriticalSection(&cs);

	for (int i = 0; i < 5000; i++)
		num -= 1;

	LeaveCriticalSection(&cs);

	return 0;
}

如果把cs物件的相關語句註釋掉,結果就會出錯,因為沒有保證執行緒互斥訪問num。

核心態的同步方法

互斥量

建立互斥量:

HANDLE CreateMutex(LPSECURITY_ATTRIBUTES lpMutexAttributes, BOOL bInitialOwner, LPCTSTR lpName);

引數含義:

lpMutexAttributes:傳遞安全相關的配置資訊,傳遞NULL時表示使用預設配置。

bInitialOwner:TRUE表示建立的互斥量物件屬於呼叫該函式的程序,且互斥量進入non-signaled狀態。FALSE表示不屬於任何程序,且進入signaled狀態。

lpName:用於命名互斥量物件,傳遞NULL表示建立無名的互斥量。

成功時候返回建立的互斥量物件的控制代碼,失敗時返回NULL。

銷燬控制代碼函式:

BOOL CloseHandle(HANDLE hObject);

成功時返回TRUE,失敗時候返回FALSE。

上鎖則呼叫的是WaitForSingleObject,解鎖呼叫的是ReleaseMutex。一個程序(或執行緒)呼叫WaitForSingleObject返回之後,互斥量會再次進入non-signaled狀態,因此其他呼叫WaitForSingleObject的程序(或執行緒)會阻塞等待,知道該程序呼叫ReleaseMutex。

示例:

#include<iostream>
#include<cstdio>
#include<Windows.h>
#include<process.h>
using namespace std;
const int NUMTHREAD = 50;
typedef long long ll;

ll num;
HANDLE hMutex;

unsigned WINAPI threadInc(void *arg);
unsigned WINAPI threadDec(void *arg);

int main()
{
	HANDLE tHandles[NUMTHREAD];
	
	hMutex = CreateMutex(NULL, FALSE, NULL);
	for (int i = 0; i < NUMTHREAD; i++)
	{
		if (i & 1) tHandles[i] = (HANDLE)_beginthreadex(NULL, 0, threadInc, NULL, 0, NULL);
		else tHandles[i] = (HANDLE)_beginthreadex(NULL, 0, threadDec, NULL, 0, NULL);
	}

	WaitForMultipleObjects(NUMTHREAD, tHandles, TRUE, INFINITE);
	CloseHandle(hMutex);

	cout << "Result:" << num << endl;

	return 0;
}

unsigned WINAPI threadInc(void *arg)
{
	WaitForSingleObject(hMutex, INFINITE);

	for (int i = 0; i < 5000; i++)
		num += 1;

	ReleaseMutex(hMutex);

	return 0;
}

unsigned WINAPI threadDec(void *arg)
{
	WaitForSingleObject(hMutex, INFINITE);

	for (int i = 0; i < 5000; i++)
		num -= 1;

	ReleaseMutex(hMutex);

	return 0;
}
訊號量

建立訊號量函式原型:

HANDLE CreateSemaphore(LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, LONG lInitialCount, LONG lMaxinumCount, LPCTSTR lpName);

引數含義和互斥量差不多,其中lInitialCount和lMaxinumCount分別表示訊號量初始值和最大值,顯然,初始值應小於等於最大值。

訊號量的p操作呼叫的WaitForSingleObject,訊號量為0時進入non-signaled狀態,大於0時進入signaled狀態。

v操作呼叫的ReleaseSemaphore,原型如下:

BOOL ReleaseSemaphore(HANDLE hSemaphore, LONG lReleaseCount, LPLONG lpPreviousCount);

lReleaseCount表示表示訊號量進行v操作之後,要增加的值。若增加之後超過最大值,則不增加並返回FALSE。

第三個引數用於儲存修改之前值的變數地址,不需要時傳遞NULL。

示例:

#include<iostream>
#include<cstdio>
#include<Windows.h>
#include<process.h>
using namespace std;

static HANDLE semOne, semTwo;
static int num;

unsigned WINAPI read(void *arg);
unsigned WINAPI accu(void *arg);

int main()
{
	HANDLE hThread1, hThread2;
	
	semOne = CreateSemaphore(NULL, 0, 1, NULL);
	semTwo = CreateSemaphore(NULL, 1, 1, NULL);

	hThread1 = (HANDLE)_beginthreadex(NULL, 0, read, NULL, 0, NULL);
	hThread2 = (HANDLE)_beginthreadex(NULL, 0, accu, NULL, 0, NULL);

	WaitForSingleObject(hThread1, INFINITE);
	WaitForSingleObject(hThread2, INFINITE);

	CloseHandle(hThread1);
	CloseHandle(hThread2);

	return 0;
}

unsigned WINAPI read(void *arg)
{
	for (int i = 0; i < 5; i++)
	{
		cout << "Input num:";
		
		WaitForSingleObject(semTwo, INFINITE);
		cin >> num;
		ReleaseSemaphore(semOne, 1, NULL);
	}

	return 0;
}

unsigned WINAPI accu(void *arg)
{
	int sum(0);

	for (int i = 0; i < 5; i++)
	{
		WaitForSingleObject(semOne, INFINITE);
		sum += num;
		ReleaseSemaphore(semTwo, 1, NULL);
	}

	cout << "Result:" << sum << endl;

	return 0;
}
事件

事件同步物件的特點是在建立時,可以自主選擇建立auto-reset模式還是manual-reset模式。

事件的建立函式:

HANDLE CreateEvent(LPSECURITY_ATTRIBUTES lpEventAttributes, BOOL bManualReset, BOOL bInitialState, LPCTSTR lpName);

bManualReset為TRUE時建立manual-reset模式的事件物件,為FALSE時建立auto-reset模式的事件物件。

bInitialState為TRUE時建立signaled狀態的事件物件,為FALSE時建立non-signaled狀態的事件物件。

在manual-reset下WaitForsingleObject返回後,不會自動進入non-signaled狀態,因此還需要下列兩個函式:

BOOL ResetEvent(HANDLE hEvent);		//進入non-signaled狀態
BOOL SetEvent(HANDLE hEvent);		//進入signaled狀態

示例:

#include<iostream>
#include<cstdio>
#include<Windows.h>
#include<process.h>
using namespace std;
const int LEN = 100;

char str[LEN];
HANDLE hEvent;
int cntA, cntOthers;

unsigned WINAPI numberOfA(void *arg);
unsigned WINAPI numberOfOthers(void *arg);

int main()
{
	HANDLE hThread1, hThread2;
	
	hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);	//建立non-signaled狀態的manual-reset模式的事件物件
	hThread1 = (HANDLE)_beginthreadex(NULL, 0, numberOfA, NULL, 0, NULL);
	hThread2 = (HANDLE)_beginthreadex(NULL, 0, numberOfOthers, NULL, 0, NULL);
	//因為事件物件為non-signaled狀態,所以這兩個執行緒都會阻塞等待

	cout << "Input string:";
	cin >> str;
	SetEvent(hEvent);		//設定事件物件為signaled狀態,兩個執行緒會同時結束等待繼續進行,因為WaitForSingleObject並不會改變事件物件的狀態

	WaitForSingleObject(hThread1, INFINITE);
	WaitForSingleObject(hThread2, INFINITE);

	ResetEvent(hEvent);
	CloseHandle(hEvent);

	cout << "Numbr of A:" << cntA << endl;
	cout << "Number of others:" << cntOthers << endl;

	return 0;
}

unsigned WINAPI numberOfA(void *arg)
{
	WaitForSingleObject(hEvent, INFINITE);
	for (int i = 0; str[i] != 0; i++)
	{
		if (str[i] == 'A') cntA++;
	}
	return 0;
}

unsigned WINAPI numberOfOthers(void *arg)
{
	WaitForSingleObject(hEvent, INFINITE);
	for (int i = 0; str[i] != 0; i++)
	{
		if (str[i] != 'A') cntOthers++;
	}
	return 0;
}

相關推薦

一種Windows執行同步的實現方法

一種Windows下執行緒同步的實現方法 Windows下的多執行緒與執行緒同步概述 多工是一個作業系統可以同時執行多個程式的能力。基本上,作業系統使用一個硬體時鐘為同時執行的每個程序分配“時間片”。如果時間片足夠小,並且機器也沒有由於太多的程式而超負荷,那麼在使用者看來,所

Windows執行同步

核心物件首先介紹核心物件的概念。應用程式在執行過程中會建立各種資源,如程序,執行緒,檔案,互斥量等等。這些資源均由作業系統管理。作業系統管理這些資源的方式就是:記錄這些資源的相關資訊,會在其內部生成資料塊。每種資源需要維護的資訊不同,因此每種資源擁有的資料塊格式也不同。這類資

window執行同步之(Critical Sections(關鍵程式碼段、關鍵區域、臨界區域)----轉載

轉載:https://www.cnblogs.com/cyblogs/p/9948379.html    關鍵區域(CriticalSection) 臨界區是為了確保同一個程式碼片段在同一時間只能被一個執行緒訪問,與原子鎖不同的是臨界區是多條指令的鎖定,而原子

windows執行同步技術

轉載自: 天極網,, 摘要: 多執行緒同步技術是計算機軟體開發的重要技術,本文對多執行緒的各種同步技術的原理和實現進行了初步探討。  關鍵詞: VC++6.0; 執行緒同步;臨界區;事件;互斥;訊號量;   閱讀目錄:   使執行緒同步   臨界區  管理事件核心物件  

Linux執行同步的幾種方法

Linux下提供了多種方式來處理執行緒同步,最常用的是互斥鎖、條件變數和訊號量。一、互斥鎖(mutex)   鎖機制是同一時刻只允許一個執行緒執行一個關鍵部分的程式碼。 1. 初始化鎖   int pthread_mutex_init(pthread_mutex_t *m

Linux執行同步的幾種常見方法

Linux下提供了多種方式來處理執行緒同步,最常用的是互斥鎖、條件變數和訊號量。一、互斥鎖(mutex)  鎖機制是同一時刻只允許一個執行緒執行一個關鍵部分的程式碼。 1. 初始化鎖  int pthread_mutex_init(pthread_mutex_t *mutex

windows執行同步(利用事件物件,互斥物件,關鍵程式碼段)實現

一:利用事件實現執行緒同步 1.createthread函式的用法 hThread = CreateThread(&security_attributes, dwStackSize, ThreadProc,pParam, dwFlags, &idThre

windows執行同步

互斥量(鎖)適用範圍:可以跨程序同步,還可以用來保證程式只有一個互斥鎖例項執行(建立命名互斥量),也可以用來做執行緒間的同步如果用於程序間同步,一個執行緒建立互斥量物件後,另一個程序只需要獲取互斥量就可以,可以用OpenMutex(MUTEX_ALL_ACCESS,FALSE

Windows執行漫談——.NET執行同步之Interlocked和ReadWrite鎖

摘要: 本系列意在記錄Windwos執行緒的相關知識點,包括執行緒基礎、執行緒排程、執行緒同步、TLS、執行緒池等。 這篇來說說靜態的Interlocked類和ReadWrite鎖 .NET中的Interlock

Windows程式設計之執行同步

Windows程式設計中執行緒同步的主要機制:互斥、事件、訊號量、可等待定時器,不說了,直接上程式碼: // ThreadSync.cpp : 定義控制檯應用程式的入口點。 // #include "stdafx.h" #include <windows.h>

Windows原理】執行同步-訊號量

#include "stdafx.h" #include <windows.h> int g_num = 0; HANDLE g_hSemaphore = nullptr; DWORD WINAPI ThreadProc(LPVOID lpParam) { for

Windows原理】執行同步-事件物件2

#include "stdafx.h" #include <windows.h> HANDLE g_hEventA = nullptr; // 三個只讀執行緒 DWORD WINAPI ThreadProcA(LPVOID lpParam) { WaitForSin

Windows原理】執行同步-事件物件

#include "stdafx.h" #include <windows.h> HANDLE g_hEventA = nullptr; HANDLE g_hEventB = nullptr; DWORD WINAPI ThreadProcA(LPVOID lpParam) {

執行同步(windows平臺):事件

一:介紹 事件Event實際上是個核心物件,事件分兩種狀態:激發狀態和未激發狀態。分兩種型別:手動處置事件和自動處置事件。 手動處置事件被設定為激發狀態後,會喚醒所有等待的執行緒,一直保持為激發狀態,

Windows執行同步之互斥鎖(Mutex)

執行緒同步的方式和機制臨界區、互斥區、事件、訊號量四種方式臨界區(Critical Section)、互斥量(Mutex)、訊號量(Semaphore)、事件(Event)的區別1、臨界區:通過對多執行緒的序列化來訪問公共資源或一段程式碼,速度快,適合控制資料訪問。在任意時刻

Windows執行(七)使用互斥量無法解決執行同步問題

互斥量介面: 1.建立互斥量: HANDLE CreateMutex ( LPSECURITY_ATTRIBUTES lpMutexAttributes,//安全控制,一般為NULL BOOL bInitialOwner, //

Windows執行(六)使用事件機制解決執行同步問題

事件相關函式: 1.建立事件:CreateEvent HANDLE CreateEvent ( LPSECURITY_ATTRIBUTES lpEventAttributes, BOOL bManualReset, BOOL bInitialState,

Windows執行(五)關鍵段無法解決執行同步問題

關鍵段: 關鍵段型別CRITICAL_SECTION 1.初始化關鍵段: void InitializeCriticalSection(LPCRITICAL_SECTION lpCriticalSection); 2.銷燬關鍵段: void DeleleCriticalSe

Windows執行(四)執行同步與互斥問題

執行緒同步與互斥的測試函式如下所示: #include <stdio.h> #include <process.h> #include <Windows.h> #define THREAD_NUM 10 unsigned long g_nNum

C++執行同步的四種方式(Windows

為什麼要進行執行緒同步?   在程式中使用多執行緒時,一般很少有多個執行緒能在其生命期內進行完全獨立的操作。更多的情況是一些執行緒進行某些處理操作,而其他的執行緒必須對其處理結果進行了解。正常情況下對這種處理結果的瞭解應當在其處理任務完成後進行。   如果不