程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> 淺談線程同步

淺談線程同步

編輯:C++入門知識

現代操作系統都支持多線程操作了,多線程操作帶來的一個麻煩就是多個線程對共享數據的訪問。假設我們有線程A

和線程B,它們需要訪問同一內存區域,線程A寫,線程B讀。一般情況下我們是希望線程A寫操作完成後再進行讀操

作或者線程B讀操作完成後我們再進行寫操作。但是在多線程中,可能由於線程A分配的時間片用完了或者其他原因導

致線程A的寫操作還沒完成就調用線程B來對這塊共享內存進行讀操作,也有可能在線程B的讀操作還沒完成就調用線

程A來對這塊共享內存進行寫操作,這些情況都有可能導致嚴重的邏輯錯誤。為了解決這一現象,我們就需要一種機

制使得各個線程能夠協同工作,這就是我們所講的線程同步機制了。

 


Windows系統中用於線程同步的常用機制有:

互斥對象(Mutex)

事件對象(Event)

信號量(Semaphore)

臨界區(critical section)

可等待計時器(Waitable Timer)

 


在學習線程同步之前,我們需要先來了解下同步過程中最重要的兩個概念:同步對象和等待函數。

同步對象主要有(Mutex、Event、Semaphore、critical section)。同步對象一般具有兩種狀態:標志的和未標志的。線

程根據是否已經完成操作將同步對象設置為標志的或未標志的。

而等待函數的功能是專門用於等待同步對象狀態改變。一個線程調用等待函數後執行會暫停,直到同步對象的狀態變

為標志的之後,等待函數才會返回,線程才能繼續執行下去。

關於上面所講的幾種主要同步對象的概念,這篇臨界區,互斥量,信號量,事件的區別講的很詳細,不懂的朋友可以

去肯看。

 


線程同步的過程:

1、在需要進行線程同步的進程中定義某種同步對象,同步對象必需是全局的,以保證需要同步的所有線程都可以訪

     問到同步對象。

2、開始時,所有的線程相互獨立地運行

3、當某一線程(為了方便描述設為線程A)需要訪問共享資源時,若同步對象為“未標志的”,繼續等待;反之,線程A將

     同步對象設為“未標志的”,並對共享資源進行訪問,訪問結束後再將同步對象設為“標志的”使得其它線程可以訪問

     共享資源。

 


為了便於理解線程同步的過程,我們可以把我們需要訪問的共享資源當成是一件放在房間裡的東西,而同步對象當成

是門上的鎖,而需要訪問資源的線程就可以當做是取東西的人了,“標志的”狀態表示門是開的,“未標志的”狀態表示

門是鎖著的,而此時鑰匙在進去的那個人手裡。當某人進入房間後,就將門鎖上,其他人就無法進入了,只有等這個

人出來之後才能進入。

 


下面是一個我自己寫的利用事件對象來同步訪問共享內存實例:


[cpp] 
#include <windows.h>  
#include <stdio.h>  
#include <string.h>  
 
TCHAR szSharedBuffer[100] = {0};  //共享內存  
HANDLE hEvent;                    //事件對象句柄  
 
DWORD WINAPI ThreadForWrite (LPVOID lpParam); 
DWORD WINAPI ThreadForRead (LPVOID lpParam); 
 
int main() 

    HANDLE hWrite; 
    HANDLE hRead; 
 
    hEvent = CreateEvent(NULL, FALSE, FALSE, NULL); 
 
    hWrite = CreateThread(NULL, 
        0, 
        ThreadForWrite, 
        0, 
        0, 
        NULL); 
    hRead = CreateThread(NULL, 
        0, 
        ThreadForRead, 
        0, 
        0, 
        NULL); 
 
    SetEvent(hEvent); 
 
    while(1); 
    return 0; 

 
DWORD WINAPI ThreadForWrite(LPVOID lpParam) 

    while (1) 
    { 
        WaitForSingleObject(hEvent, INFINITE); 
        printf("Please input the shared chars: "); 
        scanf("%s", szSharedBuffer); 
        SetEvent(hEvent); 
    } 
    return 0; 

 
DWORD WINAPI ThreadForRead(LPVOID lpParam) 

    while (1) 
    { 
        WaitForSingleObject(hEvent, INFINITE); 
        if (!strlen(szSharedBuffer)) 
            printf("The shared chars is null now!\n"); 
        else 
            printf("The shared chars is %s\n", szSharedBuffer); 
        SetEvent(hEvent); 
    } 
    return 0; 

#include <windows.h>
#include <stdio.h>
#include <string.h>

TCHAR szSharedBuffer[100] = {0};  //共享內存
HANDLE hEvent;                    //事件對象句柄

DWORD WINAPI ThreadForWrite (LPVOID lpParam);
DWORD WINAPI ThreadForRead (LPVOID lpParam);

int main()
{
 HANDLE hWrite;
 HANDLE hRead;

 hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);

 hWrite = CreateThread(NULL,
  0,
  ThreadForWrite,
  0,
  0,
  NULL);
 hRead = CreateThread(NULL,
  0,
  ThreadForRead,
  0,
  0,
  NULL);

 SetEvent(hEvent);

 while(1);
 return 0;
}

DWORD WINAPI ThreadForWrite(LPVOID lpParam)
{
 while (1)
 {
  WaitForSingleObject(hEvent, INFINITE);
  printf("Please input the shared chars: ");
  scanf("%s", szSharedBuffer);
  SetEvent(hEvent);
 }
 return 0;
}

DWORD WINAPI ThreadForRead(LPVOID lpParam)
{
 while (1)
 {
  WaitForSingleObject(hEvent, INFINITE);
  if (!strlen(szSharedBuffer))
   printf("The shared chars is null now!\n");
  else
   printf("The shared chars is %s\n", szSharedBuffer);
  SetEvent(hEvent);
 }
 return 0;
}

當然這裡只是對線程同步進行一個簡單的說明,真正要掌握線程同步比這裡所寫的要復雜的多。這將在以後的學習中

慢慢補充。

 


補充:

在上面這個例子中,創建事件對象時,第二個參數我設置的是FALSE,也就是說將事件自動重置。後來我自己改用

設置為TRUE,結果出了問題。後來想起來設置為TRUE的話,需要我們手動設置。於是在WaitForSingleObject函數

後面加了ResetEvent函數。本以為這樣就可以解決問題了,但還是有問題。後來在網上問了下別人也找了點書看才知

道了原因。

 


這種做法存在兩個問題,一個問題是,在單CPU平台下,同一時刻只能有一個線程在運行,假設線程ThreadForWrite

先執行,它得到事件對象:hEvent,但是如果正好這時它的時間片終止了,於是輪到線程ThreadForRead執行,但因

為現在在線程ThreadForWrite中,ResetEvent函數還沒有被執行,所以該事件對象仍然處於“標志的”狀態,因此線程

ThreadForRead就可以得到該事件對象,也就是說,此時兩個線程都可以訪問共享資源,於是結果就無法預料了。

 


第二個問題,當把這段程序移植到多CPU平台上時,兩個線程就可以同時運行,這時再主函數裡調用SetEvent函數將

其設置為”標志的“狀態已經沒多大意義了,因為這兩個線程都已經可以訪問共享資源了,而且是同時使用。

 


看來以後實現線程同步時還是老實點使用自動重置的事件對象。

 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved