Thread synchronization in user mode
This presentation is the property of its rightful owner.
Sponsored Links
1 / 35

Thread Synchronization in User Mode PowerPoint PPT Presentation


  • 113 Views
  • Uploaded on
  • Presentation posted in: General

Thread Synchronization in User Mode. 如何處理多個 thread 合作同步的問題 ?. Outline. 出了甚麼問題 ? Without interrupt ( 保證 incrementing of the value is done atomically) Interlocked functions 簡介 InterLockedExchanged 使用範例 InterLockedChange 使用範例 InterLockedCompareExchage 簡介 Cache lines. 速度非常快.

Download Presentation

Thread Synchronization in User Mode

An Image/Link below is provided (as is) to download presentation

Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author.While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server.


- - - - - - - - - - - - - - - - - - - - - - - - - - E N D - - - - - - - - - - - - - - - - - - - - - - - - - -

Presentation Transcript


Thread synchronization in user mode

Thread Synchronization in User Mode

如何處理多個thread 合作同步的問題?


Outline

Outline

  • 出了甚麼問題?

  • Without interrupt (保證 incrementing of the value is done atomically)

  • Interlocked functions簡介

    • InterLockedExchanged 使用範例

    • InterLockedChange 使用範例

    • InterLockedCompareExchage 簡介

  • Cache lines

速度非常快

Waste CPU time


Outline1

Outline

  • Advanced Thread Synchronization

    • Critical Sections


Thread synchronization in user mode

出了甚麼問題?

  • 兩個 thread 同時存取一個global variable

long g_x=0;

DWORD WINAPI Thread1(PVOID pvParam){

g_x++;

return(0);

}

DWORD WINAPI Thread2(PVOID pvParam){

g_x++;

return(0);

}

同時執行的造成 g_x 的結果不可預期!

g_x++;

相當於

MOV EAX,[g_x]

INC EAX

MOV [g_x], EAX


Without interrupt incrementing of the value is done atomically

同時執行g_x++;

有可能

MOV EAX,[g_x]

INC EAX

MOV EAX,[g_x]

INC EAX

MOV [g_x], EAX

MOV [g_x], EAX

EAX=0

EAX=1

EAX=0

EAX=1

g_x=1

g_x=1

Without interrupt (保證 incrementing of the value is done atomically)

全區域變數

組合語言指令交互執行

EAX 加一的結果,

還沒存起來就被蓋掉了

g_x++ 有必要不能被中斷!!


Thread synchronization in user mode

變數的位址

遞增的值(可以是負的)

LONG InterlockedExchangeAdd( PLONG plAddend, LONG lIncrement);

Interlocked functions簡介:InterlockedExchangeAdd()

  • Incrementing of the value is done atomically

long g_x=0;

DWORD WINAPI Thread1(PVOID pvParam){

InterlockedExchangeAdd( &g_x , 1);

return(0);

DWORD WINAPI Thread2(PVOID pvParam){

InterlockedExchangeAdd( &g_x , 1);

return(0);

}

Atomically increase

Atomically increase


Interlockedexchanged add

InterLockedExchangedAdd使用範例

int APIENTRY WinMain(…){

HANDLE hThread[2];

DWORD dwThread1ID;

hThread[0]=chBEGINTHREADEX(NULL,0,Thread1,(PVOID)NULL,0,&dwThread1ID);

DWORD dwThread2ID;

hThread[1]=chBEGINTHREADEX(NULL,0,Thread2,(PVOID)NULL,0,&dwThread2ID);

WaitForMultipleObjects(2,hThread,TRUE,INFINITE);

// 印出結果

TCHAR Message[100];

wsprintf(Message,_T("變數 g_x=%ld"),g_x);

MessageBox(NULL,Message,_T("建立兩個 thread 存取變數 g_x"),MB_OK);

// 關閉 Handle

CloseHandle(hThread[0]);

CloseHandle(hThread[1]);

return 0;

}

建立兩個 thread 存取變數 g_x

等待兩個 handle 全部都signaled才會繼續執行

避免: primary thread 結束,強迫所有 thread Exit

InterlockedExcangeAdd 簡單範例


How do the interlocked functions work

How do the interlocked functions work ?

  • Depends on the CPU

    • X86  assert a 硬體訊號 on the bus, 以防止其他 CPU 存取相同的記憶體位址

  • InterlockedChangeAdd 特性

    • 多個 CPU 亦能保證 atomic 性質

    • 速度非常快 (less than 50 cycles)

    • 不會切換到 kernel model


Interlocked functions interlocked ex change

欲更動變數的位址

傳回改變前的值

欲設定的值

LONG InterlockedExchange( PLONG plAddend, LONG lValue);

g_fResourceInUse=TRUE

表示有人在使用

Interlocked functions簡介: InterlockedExChange()

設定 global 變數

Spin lock

在還沒有得到權限前, while 會一直耗盡 CPU time

BOOL g_fResourceInUse=FALSE;

void Fun1(){

while( InterlockedExchange( & g_fResourceInUse, TRUE) == TRUE)

Sleep(0);

InterlockedExchange( & g_fResourceInUse, FALSE);

}

等待進入存取資源

若之前是 TRUE,

則等待

放棄剩下的 time slices

進入 ready 狀態

存取資源

觧開 Spin lock 鎖


Thread synchronization in user mode

補充

全區域共用變數

g_fResourceInUse=FALSE;

void ThreadFun1(){

while(g_fResourceInUse== TRUE)

Sleep(0);

// 設定進入

g_fResourceInUse=TRUE;

// 設定離開

g_fResourceInUse=FALSE;

}

void ThreadFun2(){

while(g_fResourceInUse== TRUE)

Sleep(0);

// 設定進入

g_fResourceInUse=TRUE;

// 設定離開

g_fResourceInUse=FALSE;

}

存取共用檔案 (或資料)

存取共用檔案 (或資料)

防止兩條 thread 同時存取共用資料失敗

 兩條 thread 同時都發現 g_fResourceInUse = FALSE

同時進入存取

設定與檢查必須同時進行


Interlocked ex change

InterlockedExChange的注意事項

  • Spin lock 非常浪費 CPU time

  • 多 CPUs 的情況下,欲存取的資料與 lock variable 在不同的cache line才會有效率(避免 race condition)

cpu1

cpu2

Lock variable

共用資源

Cache line:

一般 CPU 執行程式會先把一小段程

式由 Memory 放到 Cache 中, 以增

進效率.


Cpu time

避免耗費大量 CPU time 的策略

  • 讓給同樣 priority 等級的 thread 執行

  • 若while 執行4000次,還沒搶到資源, 則進入 Kernel Mode 等待 (consuming no CPU time)

while( InterlockedExchange( & g_fResourceInUse, TRUE) == TRUE)

Sleep(0);

策略: 若搶不到資源, 則 sleep 一段時間. 若還是搶不到資源,則給予更長的時間.

比較好的策略是

Wait function

WaitForSignalObject

WaitForMultipleObjects


Interlocked functions atomic test and set operation

LONG InterlockedCompareExchange ( PLONG plDestination,

LONG lExchange

LONG lComparand);

1 (A)

2 (B)

3 (C)

訂正

Interlocked functions簡介: Atomic test and set operation

資料版本: 如果 long值 (*A) == long值 C, 則 (*A) = B

(*1)

3

if( == )

else

不變

(*1) = 2

指標版本: 如果 位址 (*A) == 位址 C, 則 (*A) = B

LONG InterlockedCompareExchangePointer ( PVOID* A,

PVOID B

PVOID C);

(*A)

C

if( == )

else

不變

(*A)=

B


Cache lines

32-byte

32-byte

Cache lines 觀念

X86 CPU

  • 為了增進 CPU 執行效率, 一次會讀取 32 or 64-byte 到 cache中,並且 aligned on 32 or 64-byte boundary

  • 考量 multiprocessor

CPU1

CPU2

Memory Update 問題

程式碼片段

若CPU1更改了其中一個變數的值, 必須通知 CPU2知道

複製

複製

複製

複製


Cache lines1

Cache lines 衍生出的效率問題

  • 應用程式中的資料應該以

    • Cache-line 大小為單位 group 起來

    • 並且放到 cache-line boundaries

  • ReadOnly 的變數與 Read/Write 變數分開

理由是:

不希望兩個 CPU 交互通知

寫就會通知


Poor designed data structure

// 客戶資料

struct CustInfo {

DWORD dwCustomerID; // 顧客號碼

int nBalanceDue; // 結餘款

char szName[100]; // 客戶名稱

FILETIME ftLastOrderDate; // 最後購物時間

};

Most Read Only

// 客戶資料

struct CustInfo {

DWORD dwCustomerID; // 顧客號碼

char szName[100]; // 客戶名稱

int nBalanceDue; // 結餘款

FILETIME ftLastOrderDate; // 最後購物時間

};

Read-Write

比較好的策略

Most Read Only

Most Read Only

Most Read Only

Read-Write

A. 強迫下面資料放在另一個 cache line

Read-Write

Read-Write

B. 強迫以後的資料放在另一個 cache line

Poor designed data structure


Cache line

強迫把資料放在另一個 cache line

#define CACHE_ALIGH 32 // 若是 X86 型系統

#define CACHE_PAD (Name, BytesSoFar) \

BYTE Name[CACHE_ALIGN - ((BytesSoFar) % CACHE_ALIGN)]

1

計算前面的資料,剩下

的 Bytes 數目

X86 下, 一個 Cache-line 包含 32 bytes

2

只要配置 32- ((BytesSoFar)%32

Byte 的陣列就可以補滿了


Thread synchronization in user mode

完整的程式碼

// 客戶資料

struct CustInfo {

DWORD dwCustomerID; // 顧客號碼

char szName[100]; // 客戶名稱

CACHE_PAD(X1,sizeof(DWORD)+100);

int nBalanceDue; // 結餘款

FILETIME ftLastOrderDate; // 最後購物時間

CACHE_PAD(X2, sizeof(int)+sizeof(FILETIME));

};

Most Read Only

Read-Write

有興趣的人,可以看網頁上的範例

Microsoft-specific storage-class attributes

資料成員的位址對齊新語法

__declspec(align(32))


Thread synchronization in user mode

我們需要一個機制,讓執行緒可以不用浪費 CPU時間 的等待存取一個共享的資源。


Critical sections

Critical Sections 概念

  • 甚麼是Critical Section?

    • 是一程式區段, 而這個程式區段必須擁有某共用資源的權限才能執行

    • 你可以放心的執行 Critical Section 的程式碼,

      絕不會有其他的 thread 同時執行你所在的code

    • 你的 thread 會被 preempt 換其他的thread 執行, 但是想要進入 Critical Section 的thread 是不會被 schedule的

  • 系統不保證進入Critical Section thread 的順序,但OS保證公平對待所有要進入的thread


Critical section

DWORD WINAPI SecondThread(PVOID pvParam) {

while (g_nIndex < MAX_TIMES) {

g_nIndex++;

g_dwTimes[g_nIndex - 1] = GetTickCount();

}

return(0);

}

來看看, 不用Critical Section 會發生的問題

const int MAX_TIMES = 1000;

int g_nIndex = 0;

DWORD g_dwTimes[MAX_TIMES];

存取共用資源

存取共用資源

第一個 thread

DWORD WINAPI FirstThread(PVOID pvParam) {

while (g_nIndex < MAX_TIMES) {

g_dwTimes[g_nIndex] = GetTickCount();

g_nIndex++;

}

return(0);

}

第二個 thread


Critical section1

加入 Critical Section 解決問題

CRITICAL_SECTION g_cs;

注意: 取出位址

DWORD WINAPI FirstThread(PVOID pvParam) {

while (g_nIndex < MAX_TIMES) {

EnterCriticalSection( &g_cs);

EnterCriticalSection( &g_cs);

g_dwTimes[g_nIndex] = GetTickCount();

g_nIndex++;

LeaveCriticalSection( &g_cs);

LeaveCriticalSection( &g_cs);

}

return(0);

}

設定 Critical Section 起始

設定 Critical Section 結束

DWORD WINAPISecondThread(PVOID pvParam) {

while (g_nIndex < MAX_TIMES) {

EnterCriticalSection( &g_cs);

g_nIndex++;

g_dwTimes[g_nIndex - 1] = GetTickCount();

LeaveCriticalSection( &g_cs);

}

return(0);

}


Critical section2

存取共享資源的程式碼一定要用Critical Section 包起來

沒有使用Critical

Section, 可以直接

存取共用資源

const int MAX_TIMES = 1000;

int g_nIndex = 0;

DWORD g_dwTimes[MAX_TIMES];

CRITICAL_SECTION g_cs;

違規

遵守規定

存取共用資源

第一個 thread

DWORD WINAPI FirstThread(PVOID pvParam) {

while (g_nIndex < MAX_TIMES) {

EnterCriticalSection( &g_cs);

g_dwTimes[g_nIndex] = GetTickCount();

g_nIndex++;

LeaveCriticalSection( &g_cs);

}

return(0);

}

第二個 thread

DWORD WINAPI SecondThread(PVOID pvParam) {

while (g_nIndex < MAX_TIMES) {

g_nIndex++;

g_dwTimes[g_nIndex - 1] = GetTickCount();

}

return(0);

}


Critical section3

我們來看看Critical Section 的處理細節

  • 在使用 Critical Section 之前,要先作初始化的動作

  • 初始化後, Process 中的thread 才能呼叫

    • EnterCriticalSection(), TryEnterCriticalSection, LeaveCriticalSection()

  • 你不該去更改 Critical object的內容,你只要呼叫相關的function 操作即可

VOID InitializeCriticalSection(LPCRITICAL_SECTION lpCriticalSection );

會 block

return immediately


Critical section4

補充

Critical Section 的處理細節

  • 當 thread 已經擁有 critical section後,亦可以呼叫 EnterCriticalSection()

  • 當擁有 critical section 的 thread 被 terminate掉時, critical section 的狀態沒有定義

  • 若 critical section object 在呼叫 LeaveCriticalSection 之前就被 delete 掉,那麼那些在外面等待的 thread 其狀態沒有定義

防止等待自己的 dead lock 情事發生

呼叫 DeleteCriticalSection(&CriticalSection)


Thread synchronization in user mode

CRITICAL_SECTION g_cs;

… WinMain(…) {

InitializeCriticalSection( &g_cs);

DeleteCriticalSection (&g_cs);

}

建立第一個 thread

建立第二個 thread

所以你的程式架構,應該長成這樣

DWORD WINAPI FirstThread(PVOID pvParam) {

bool bQuit=false;

while(!bQuit){

EnterCriticalSection( &g_cs);

if(q_nIndex<MAX_TIMES){

g_dwTimes[g_nIndex] = GetTickCount();

g_nIndex++;

}else{

bQuit=true;

}

LeaveCriticalSection( &g_cs);

}

return(0);

}

Thread1.cpp

WinMain.cpp

DWORD WINAPISecondThread(PVOID pvParam) {

bool bQuit=false;

while (!bQuit) {

EnterCriticalSection( &g_cs);

if(q_nIndex<MAX_TIMES){

g_nIndex++;

g_dwTimes[g_nIndex - 1] = GetTickCount();

}else{ bQui=true;}

LeaveCriticalSection( &g_cs);

}

return(0);

}

Thread2.cpp

CriticalSectionDemo


Entercriticalsection

EnterCriticalSection 的處理流程

Yes 目前有其他的thread

正在存取資源

是否有執行緒正在存取資源

呼叫的 thread 被 blocked 住

NO

進入Waitting 狀態

(不會浪費CPU time)

更新 CriticalSection

中的變數,指明目前thread

已經存取資源

Return


Starve

饑餓(starve) 狀態

  • 當一個 thread 一直無法得到 CPU 執行

  • 你可以使用

    BOOL TryEnterCriticalSection(PCRITICAL_SECTION pcs);

  • 等待進入critical section 的 thread 不會出現 starve

  • Time out

大約 30 天

HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager

Win2000 only

事先檢查是否可以進入Critical Section, 以免等待!!


Criticalsetion

進入 CriticalSetion 的流程

EnterCriticalSection

DWORD WINAPI FirstThread(PVOID pvParam) {

while (g_nIndex < MAX_TIMES) {

EnterCriticalSection( &g_cs);

g_dwTimes[g_nIndex] = GetTickCount();

g_nIndex++;

LeaveCriticalSection( &g_cs);

}

return(0);

}

在CriticalSection中,遞增1,表示目前有人在裡面

LeaveCriticalSection

在CriticalSection中,遞減1,

=0  找出所有在外面等

待的thread,公平的

選一個進入排程


Critical section spinlock

效率的考量: 合併 Critical Section 與 SPINLOCK

  • 當thread進入一個已經被佔用critical section

     Waiting State (由User mode 轉到 Kernel model)

  • 多CPU的情況

    • 讓 thread 先用 spin lock 在 user mode 等待.

       超過一定嘗試次數後, 才進入Kernel model waiting

很慢

耗費時間 (about 1000 CPU cycle)

單CPU 下, 你用 SPIN lock 等待一樣沒效率!

因為在自己的 time-slice 下沒使用完前,無法讓其他的 thread 進入critical section

BOOL InitializeCriticalSectionAndSpinCount( PCRITICAL_SECTION pcs,

DWORD dwSpinCount);

single CPU下 spin count=0

與其空轉,不如讓出 time-slice 先讓別人執行

迴旋的次數


Useful tips and techniques

Useful Tips and Techniques

分開存取不同資源

int g_nNums[100];

CRITICAL_SECTION g_cs1;

TCHAR g_cChars[100];

CRITICAL_SECTION g_cs2;

DWORD WINAPI MyThread(PVOID pvParam) {

EnterCriticalSection(& g_cs1);

for (int x = 0; x < 100; x++)

g_nNums[x] = 0;

LeaveCriticalSection(& g_cs1);

EnterCriticalSection(& g_cs2);

for (x = 0; x < 100; x++)

g_cChars[x] = TEXT('X');

LeaveCriticalSection(& g_cs2);

return(0);

}

  • 一個 share resource 只用一個 critical section保護.

  • 以免process

    霸佔所有資源, 產生飢餓問題

共享資源1號

共享資源2號

1

2


Thread synchronization in user mode

當其他Thread 也要存取時,要注意順序一樣,才不會造成死結

DWORD WINAPI Other( …) {

EnterCriticalSection(& g_cs2);

EnterCriticalSection(& g_cs1);

for (int x = 0; x < 100; x++)

g_nNums[x] = g_cChars[x];

LeaveCriticalSection(& g_cs1);

LeaveCriticalSection(& g_cs2);

return(0);

}

若要同時存取資源,怎麼辦?

同時存取資源

int g_nNums[100];

CRITICAL_SECTION g_cs1;

TCHAR g_cChars[100];

CRITICAL_SECTION g_cs2;

DWORD WINAPI MyThread(PVOID pvParam) {

EnterCriticalSection(& g_cs2);

EnterCriticalSection(& g_cs1);

for (int x = 0; x < 100; x++)

g_nNums[x] = g_cChars[x];

LeaveCriticalSection(& g_cs1);

LeaveCriticalSection(& g_cs2);

return(0);

}

保護

共享資源1號

保護

共享資源2號

2

1


Tip2 criticalsection

SOMESTRUCT g_s;

CRITICAL_SECTION g_cs1;

DWORD WINAPI MyThread(PVOID pvParam) {

EnterCriticalSection(& g_cs1);

SendMessage(hwndSomeWnd, WM_SOMEMSG, &g_s, 0);

LeaveCriticalSection(& g_cs1);

return(0);

}

Tip2: 進入CriticalSection 的時間不宜太久

  • 若你要 SendMessage 給某個視窗

  • 問題

    • 在離開 critical section 前,沒有人可以存取 g_s;

1

不確定目標視窗何時會收到 Message

SendMessage 會 block住


Thread synchronization in user mode

策略: 把資料複製起來,再送出去

SOMESTRUCT g_s;

CRITICAL_SECTION g_cs1;

DWORD WINAPI MyThread(PVOID pvParam) {

EnterCriticalSection(& g_cs1);

SOMESTRUCT sTemp = g_s;

LeaveCriticalSection(& g_cs1);

SendMessage(hwndSomeWnd, WM_SOMEMSG, & sTemp ,0);

return(0);

}


Thread synchronization in user mode

End


  • Login