SDL入門教程(十三) 1 多執行緒,從動畫說起

2021-05-25 13:44:57 字數 3291 閱讀 8996

1.1:簡單動畫

遊戲離不開動畫。我們考慮最簡單的情況:將乙個角色從乙個位置移動到另外乙個位置。這個行為表述給電腦就是,將乙個su***ce不斷的blit(),從起始位置的座標,移動到結束位置的座標。移動速度取決於每次blit()的座標差和blit()的時間間隔(v = ds/dt )。

我們來設計乙個函式實現這個簡單的動畫。我們需要的資料有:起始座標(int beginx, int beginy),結束座標(int endx, int endy),以及作為sdl基礎的screensu***ce視窗(const screensu***ce& screen)。一般的考慮是,將這5個資料以引數的方式傳入函式;但是一種更加通用一點的方式是,將這5種資料合併成乙個結構,這樣函式的引數形式會更加的統一,這正是觸發多執行緒的函式所需要的。在sdl中,我們通過函式:

sdl_thread 

*sdl_createthread(

int(

*fn)(

void

*), 

void

*data);

觸發多執行緒,其中所需要的函式指標形式為:

typedef 

int(

*fn)(

void*);

而void*型別的data就是函式(*fn)()需要的的資料。我們可以將任意的結構體指標,轉化為void*,作為這個函式的第二個引數需要。

因此,我們可以為我們需要的動畫函式定義乙個結構作為傳遞所有資料的載體:

struct

amnarg

}; 這樣,我們可以將amnarg物件的指標傳遞給動畫函式——考慮到多執行緒函式的需要,我們再曲折一點:先將amnarg*轉換成void*傳遞給函式,在函式內部再將其轉換回來以供呼叫。

intamn(

void

*data)

return0;

} 注意:我們這裡僅僅設定了每次blit()的位移差(ds)而沒有設定時間差(dt)。這並不意味著dt == 0,事實上,電腦處理資料是需要時間的,包括運算和顯示。我們這裡事實上將dt的設定交給了電腦,也就是說,讓電腦以其最快的速度來完成。為什麼要這麼做呢?這是為了演示多執行緒的乙個現象,賣個關子,後面解釋。:)

1.2:動畫函式在主程式中的呼叫

#include 

"su***ceclass.hpp

"#include 

"amn.hpp

"int

main(

intargc ,

char

*argv)

if( gameevent.type 

==sdl_keydown )

}screen.flip();}}

return0;

} 當這個程式執行的時候,我們會發現一些很明顯的問題:

1、移動的時候,介面不接受任何資訊。這是因為必須把amn()執行完畢才會執行到有事件響應的事件輪詢迴圈。

2、如果我們需要另外一張移動起來,我們唯一能做的事情,是修改amn()函式,而不是把amn()以不同的引數呼叫兩次——如果以不同的引數呼叫兩次,那麼移動總是有先後的——是不可能完成「同時」移動的。

1.3:建立執行緒

如果要將這個程式從主線程(主程序)呼叫函式修改為通過新建立的執行緒呼叫函式,只需要做很小的修改,即將amn((void*)&test1);修改為:

sdl_thread

*thread1 

=sdl_createthread(amn, (

void*)

&test1);

然後在return 0;之前加入清理執行緒的語句:

sdl_killthread(thread1);

這樣,程式在執行動畫的同時,事件輪詢就已經開始,我們可以隨時結束程式,sdl介面也不會出現不響應的情況。

2.1:競爭條件(race conditions)

intmain(

intargc ,

char

*argv)

if( gameevent.type 

==sdl_keydown )

}screen.flip();}}

sdl_killthread(thread1);

sdl_killthread(thread2);

return0;

} 這就是典型的race conditions的表現。還記得我說過沒有定義dt嗎,我們讓電腦以其所能達到的最快速度決定dt,換句話說,我們每乙個執行緒都試圖「咬死」cpu的運算,當然,在實際中多工的os會幫助cpu分配任務,但是如何分配卻是不確定的,因為os並不知道哪些任務需要優先執行,所以,兩個執行緒實際上在競爭電腦的效能資源,產生的結果就是不確定的。

2.2:鬆開「死咬」的cpu

void

sdl_delay(uint32 ms);

解決race conditions的方法就是給cpu足夠的時間「休息」,而這正好也是我們自己定義dt所需要的。sdl_delay()在這個時候就顯得意義重大了。當今電腦的運算速度非常非常快,以至於哪怕我們僅僅給電腦0.01秒的時間「休息」(每次迴圈中),電腦都會顯得很輕鬆了。

intamn(

void

*data)

return0;

} 說到這裡,我們不得不提及之前一直所忽略的乙個問題:我們之前凡是涉及迴圈等待事件輪詢的程式總是占用100%的cpu,這並不是因為我們真正用到了 100%的cpu效能,而是我們讓cpu陷入了「空等」(busy waiting)的尷尬境地。輪詢事件得到響應相對於迴圈等待來說,是發生得非常緩慢的事情,我們在迴圈中,哪怕是讓電腦休息0.01秒,事情都會發生本質性的改變:

while

( gameover 

==false)if

( gameevent.type 

==sdl_keydown )

}screen.flip();

}sdl_delay(

10);

} 當我們重新執行新程式的時候,我們可以看到程式對cpu的占用從100%驟降到了0%!這當然並不意味著程式就用不上cpu了,而是說,這些運算對於我們的cpu來說,實在是小菜一碟了,或者從資料上說,處理這些運算的時間與0.01秒來比較,都幾乎可以忽略不計!

2.3:gui執行緒與worker執行緒

一般提倡的模式,是將gui事件都寫在主線程中,而將純粹的運算才寫到由主線程建立的執行緒中,後者也就是所謂的worker執行緒。從另外乙個概念看,只有主線程控制著「當前視窗」,其它執行緒也許在後台,也許雖然也是在前台但是並非是我們可見的,所以,輪詢事件找不到介面。

對於丟擲的執行緒與主線程之間的通訊,我們可以通過他們共享的資料來進行控制,所以,儘管事件輪詢不能直接影響worker執行緒,但是我們仍然是可以通過主線程進行間接影響的。

**

SDL入門教程(十三) 2 初識多執行緒

intmain intargc char argv if gameevent.type sdl keydown screen.flip sdl killthread thread1 sdl killthread thread2 return0 void sdl delay uint32 ms 解決r...

Java多執行緒入門教程

死亡狀態 dead 執行緒執行完了或者因異常退出了run 方法,該執行緒結束生命週期。修飾普通方法 獲得this物件鎖 synchronized public void synchronized public static void public void 當多個執行緒同時訪問同乙個物件加x鎖的方法...

SDL入門教程(十) 5 SDL完美顯示中文

注意 請使用支援中文的ttf字型檔。5.1 構建可以正確顯示中文的sdl ttf函式 世界終於又充滿了光明!任何事情都是有答案的,不知道僅僅是因為我們還沒有找到。解決了以上一系列問題,我們終於可以不依賴mfc,完全使用自由開源的資源,讓sdl顯示中文了!我們通過ttf renderunicode 來...