多線程編程--5種方法實現線程同步


1:用Interlocked系列函數實現線程同步;

2:用CRITICAL_SECTION及其系列函數實現線程同步;

3:用RTL_SRWLOCK及其系列函數實現線程同步;

4:用事件內核對象實現線程同步;

5:用信號量內核對象實現線程同步;

 

1:用Interlocked系列函數實現線程同步實例如下:

//旋轉鎖
#include <iostream> 
using namespace std;  
#include <process.h>
#include <windows.h> 
const int threadNum=10;
HANDLE hThread[threadNum];
volatile unsigned int ISOK=0;
unsigned int _stdcall Interlocked(PVOID threadId)
{ 
    while(InterlockedExchange(&ISOK,1)==1) ; 
    cout<<"線程:"<<*(int*)threadId<<"開始"<<endl; 
    Sleep(100);
    cout<<"線程:"<<*(int*)threadId<<"結束"<<endl; 
    InterlockedExchange(&ISOK,0);  
    return 0;
}
 
void InterlockedTest()
{
    int threadId[threadNum];
    for(int i=0;i<threadNum;i++)
    {
        threadId[i]=i+1;
    }
    cout<<"1:用Interlocked系列函數實現線程同步"<<endl;
    for(int i=0;i<10;i++){ 
        hThread[i]=(HANDLE)_beginthreadex(NULL, 0, Interlocked,threadId+i, 0, NULL);  
    }
    WaitForMultipleObjects(threadNum, hThread, TRUE, INFINITE);   
    for(int i=0;i<threadNum;i++)
    {
        CloseHandle(hThread[i]);
    } 
}

InterlockedExchange確保以原子的方式操作數據。執行速度非常快,缺點是如果要同步的部分執行的時間比較長的話,while循環會一直輪詢操作,浪費CPU的時間,在單核CPU的系統中,可能會出現while一直暫用CPU導致其他線程不能修改ISOK的值,導致不能跳出while循環,出現死循環。還有就是線程的優先級問題也能導致問題。

2:用CRITICAL_SECTION及其系列函數實現線程同步實例如下:

//關鍵段 
#include <iostream> 
using namespace std;  
#include <process.h>
#include <windows.h> 
const int threadNum=10;
HANDLE hThread[threadNum]; 
CRITICAL_SECTION g_cs;//構造一個CRITICAL_SECTION實例
unsigned int _stdcall  CriticalSection(PVOID threadId)
{ 
    EnterCriticalSection(&g_cs);//進入關鍵段
    cout<<"線程:"<<*(int*)threadId<<"開始"<<endl; 
    Sleep(100);
    cout<<"線程:"<<*(int*)threadId<<"結束"<<endl; 
    LeaveCriticalSection(&g_cs);//進入關鍵段 
    return 0;
}


void CriticalSectionTest()
{
    int threadId[threadNum];
    for(int i=0;i<threadNum;i++)
    {
        threadId[i]=i+1;
    }
    InitializeCriticalSection(&g_cs);//初始化g_cs的成員 
    cout<<"2:用CRITICAL_SECTION及其系列函數實現線程同步"<<endl;
    for(int i=0;i<10;i++){ 
        hThread[i]=(HANDLE)_beginthreadex(NULL, 0, CriticalSection,threadId+i, 0, NULL);  
    }
    WaitForMultipleObjects(threadNum, hThread, TRUE, INFINITE);   
    for(int i=0;i<threadNum;i++)
    {
        CloseHandle(hThread[i]);
    } 
    DeleteCriticalSection(&g_cs);//刪除關鍵段
}

CRITICAL_SECTION同樣是以原子的方式操作數據,也只有以原子的方式操作數據才能實現線程的同步,所有實現線程同步的方法,最核心的部分就是以原子的方式操作數據,CRITICAL_SECTION執行的速度非常快,其內部有一個事件內核對象,當出現資源爭奪的時候,才會出現初始化這個事件內核對象,由於CRITICAL_SECTION執行非常快可能不會出現資源爭奪,也就沒有必要創建這個事件內核對象,這個事件內核對象創建后,會將當前線程之外的線程掛起,並記錄這些線程需要這個資源,其他線程就不會浪費CPU的時間,而這些被掛起的線程將由用戶模式變成內核模式,當這些線程需要的資源可用時,系統會將其中一個線程喚醒。

還有一點值得注意:如果要同步的代碼執行得很快,在出現爭奪資源的時候,系統把其他線程掛起,而當前線程又馬上執行完成了,系統又將掛起的線程喚醒,這個過程是非常浪費CPU的,也影響程序的性能,為了避免這種情況,可以結合旋轉鎖和CRITICAL_SECTION,先用旋轉鎖輪詢一定次數,還不能獲得資源,再將線程掛起,等待資源被釋放,系統再將線程喚醒,實現這一功能的就是方法

InitializeCriticalSectionAndSpinCount(

   LPCRITICAL_SECTION lpCriticalSection,

   DWORD dwSpinCount//旋轉鎖輪詢的次數

);

除了初始化CRITICAL_SECTION用的是方法InitializeCriticalSectionAndSpinCount,而不是方法InitializeCriticalSection,其他的都是一樣的。

3:用RTL_SRWLOCK及其系列函數實現線程同步實例如下:

//讀寫鎖 
#include <iostream> 
using namespace std;  
#include <process.h>
#include <windows.h> 
const int threadNum=10;
HANDLE hThread[threadNum]; 
RTL_SRWLOCK  lock;//構造一個CRITICAL_SECTION實例
unsigned int _stdcall  SrwLock(PVOID threadId)
{ 
    AcquireSRWLockExclusive(&lock);//進入讀寫鎖
    cout<<"線程:"<<*(int*)threadId<<"開始"<<endl; 
    Sleep(100);
    cout<<"線程:"<<*(int*)threadId<<"結束"<<endl; 
    ReleaseSRWLockExclusive(&lock);//進入讀寫鎖
    return 0;
}
 
void SrwLockTest()
{
    int threadId[threadNum];
    for(int i=0;i<threadNum;i++)
    {
        threadId[i]=i+1;
    }
    InitializeSRWLock(&lock);//初始化lock的成員 
    cout<<"3:用RTL_SRWLOCK及其系列函數實現線程同步"<<endl;
    for(int i=0;i<10;i++){ 
        hThread[i]=(HANDLE)_beginthreadex(NULL, 0, SrwLock,threadId+i, 0, NULL);  
    }
    WaitForMultipleObjects(threadNum, hThread, TRUE, INFINITE);   
    for(int i=0;i<threadNum;i++)
    {
        CloseHandle(hThread[i]);
    } 
     
}

SRWLock的目的和關鍵段是一樣的,就是對資源的保護,不讓其他線程訪問。不同的是,它區分線程是讀線程還是寫線程。我們都是知道,一個資源可以同時被多個線程同時讀,就是不能同時讀,或是讀寫。也是是說寫必須是獨占的方式,而讀可以以共享的方式訪問,如果以共享的方式訪問肯定就比CRITICAL_SECTION性能好。

4:用事件內核對象實現線程同步實例如下:

//事件
#include <iostream> 
using namespace std;  
#include <process.h>
#include <windows.h> 
const int threadNum=10;
HANDLE hThread[threadNum];
HANDLE event1; 

unsigned int _stdcall  Event(PVOID threadId)
{
    WaitForSingleObject(event1,INFINITE);
    int* p=(int*)threadId;
    cout<<"線程:"<<*p<<"開始"<<endl; 
    Sleep(100);
    cout<<"線程:"<<*p<<"結束"<<endl;  
    SetEvent(event1);
    return 1;
}

void EventTest()
{
    int threadId[threadNum];
    for(int i=0;i<threadNum;i++)
    {
        threadId[i]=i+1;
    }
    event1=CreateEvent(NULL,false,true,NULL); 
    cout<<"4:用事件內核對象實現線程同步"<<endl;     
    for(int i=0;i<threadNum;i++)
    {
        hThread[i] =(HANDLE)_beginthreadex(NULL, 0, Event ,threadId+i, 0, NULL);  
    }
    WaitForMultipleObjects(threadNum, hThread, TRUE, INFINITE);  
    for(int i=0;i<threadNum;i++)
    {
        CloseHandle(hThread[i]);
    } 
    CloseHandle(event1);
}

用內核對象實現線程同步,一個函數是必須知道的,它就是WaitForSingleObject。

DWORD WaitForSingleObject(

    HANDLE hHandle,//內核對象的句柄

    DWORD dwMilliseconds//等待時間

);

該函數會一直等待,直到被指定的內核對象被觸發為止,或是等待的時間結束返回。

CreateEvent(

    LPSECURITY_ATTRIBUTES lpEventAttributes,//安全控制

    BOOL bManualReset,//true:手動重置事件,false:自動重置事件

    BOOL bInitialState,//true:有信號,false:無信號

    LPCWSTR lpName//事件名稱

);

bManualReset為true表示事件觸發了並一直處於觸發狀態,就像打開的門,打開之后就是一直開着,沒有自動關上;false:一打開放一個進去進關了,需要用SetEvent再次觸發事件。

5:用信號量內核對象實現線程同步實例如下:

//信號量
#include <iostream> 
using namespace std;  
#include <process.h>
#include <windows.h> 
const int threadNum=10;
HANDLE hThread[threadNum];
HANDLE semaphore; 
unsigned int _stdcall  Semaphore(PVOID threadId)
{
    WaitForSingleObject(semaphore, INFINITE);  
    cout<<"線程:"<<*(int*)threadId<<"開始"<<endl; 
    Sleep(100);
    cout<<"線程:"<<*(int*)threadId<<"結束"<<endl; 
    ReleaseSemaphore(semaphore,1,NULL); 
    return 0;
}
 
void SemaphoreTest()
{
    int threadId[threadNum];
    for(int i=0;i<threadNum;i++)
    {
        threadId[i]=i+1;
    }
    semaphore=CreateSemaphore(NULL,1,1,NULL);
    cout<<"5:用信號量內核對象實現線程同步"<<endl;
    for(int i=0;i<10;i++){ 
        hThread[i]=(HANDLE)_beginthreadex(NULL, 0, Semaphore,threadId+i, 0, NULL);  
    }
    WaitForMultipleObjects(threadNum, hThread, TRUE, INFINITE);   
    for(int i=0;i<threadNum;i++)
    {
        CloseHandle(hThread[i]);
    }
    CloseHandle(semaphore);
}

信號量內核對象用來對資源進行計數。創建信號量內核對象的方法如下:

CreateSemaphore(

    LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,//安全控制

    LONG lInitialCount,//初始資源數量

    LONG lMaximumCount,//最大並發數量

    LPCWSTR lpName//號量的名稱

);

lMaximumCount表示最大並發數量,可以用來設置系統的最大並發數量,如果我們把他的值設為1,lInitialCount也設為1,就是只有一個資源,且每次只能一個線程訪問,這樣就可以實現線程同步。

在實現線程同步時,建議用方法2和方法3,如不能解決你的需求,再用方法4,方法5,用內核對象實現的線程同步性能要差一些。

多線程編程5中實現線程同步的方法介紹差多就到此了,大家可能還有一些疑問,可以看看我之前關於多線程基礎知識的一些介紹:

Windows線程基礎

Windows內核對象簡介

Windows幾種線程同步方法介紹


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM