最近有了很多想法,想把我用過的東西都吃透,這樣才不會變成所謂的“樣樣通樣樣松”。我是新手,老鳥請飄過,當然,這篇小心得如果有什麼毛病,還請指出來。先行謝過!
其實我本來想把博客當作自己的日記,記錄下學習的點點滴滴,寫下的就代表是學會的東西,人家說好記性不如爛筆頭嘛。
一直以來對多線程這塊就迷迷糊糊的,用得不太多,即便是用了,也是把以前寫的代碼拿出來,稍微修改一下,就適應了新的需求。也看過一些資料,但都沒實踐過,所以就馬馬虎虎地,能夠適應工作需求就得過且過。其實這種思想是非常錯誤的。做技術一定要踏實,否則就無法成長。
閒話少說書歸正傳,接下來的幾篇就把多線程的東西學習、總結一下。先上一段萬金油:
線程:有時被稱為輕量級進程(Lightweight Process,LWP),是程序執行流的最小單元。一個標准的線程由線程ID,當前指令指針(PC),寄存器集合和堆棧組成。另外,線程是進程中的一個實體,是被系統獨立調度和分派的基本單位,線程自己不擁有系統資源,只擁有一點在運行中必不可少的資源,但它可與同屬一個進程的其它線程共享進程所擁有的全部資源。一個線程可以創建和撤消另一個線程,同一進程中的多個線程之間可以並發執行。由於線程之間的相互制約,致使線程在運行中呈現出間斷性。線程也有就緒、阻塞和運行三種基本狀態。每一個程序都至少有一個線程,那就是程序本身。
自從有了線程,這個世界就變得吵起來了,線程的周期、調度與優先級、資源共享、線程同步、守護線程、死鎖、信號量。。後面咱們再慢慢研究這些東西吧,今天先來簡單明白線程到底是什麼,怎麼應用在編程中。
老慣例,上個程序吧。建立一個標准的基於對話框的MFC程序,拖一個edit控件和一個button在上面,資源命名分別為ID_EDIT_NUMBER和ID_BUTTON_START。給edit控件關聯一個變量CEdit * m_editNumber。弄差不多這個樣子就行。
先寫一個線程函數,告訴電腦在這個線程裡要做什麼。
- DWORD _stdcall ThreadProc(LPVOID lpParameter)
- {
- CMultithreadTestDlg * dlg = (CMultithreadTestDlg*) lpParameter;
- CString szCounter;
- for(int i = 0; i < 10000; i++)
- {
- szCounter.Format(_T("%d"), i);
- dlg->m_editNumber.SetWindowTextW(szCounter);
- szCounter.ReleaseBuffer();
- }
- return 0;
- }
很簡單,就是讓edit控件顯示不停增加的數字。
接下來就要想辦法啟動這個線程。
在CMultithreadTestDlg中添加一個成員變量HANDLE m_hThread。雙擊start按鈕,編寫按鈕的單擊事件。
- void CMultithreadTestDlg::OnBnClickedButtonStart()
- {
- // TODO: Add your control notification handler code here
- m_hThread = CreateThread(NULL, 0, ThreadProc, this, 0, NULL);
- }
CreateThread函數的原型如下:
- HANDLE WINAPI CreateThread(
- __in_opt LPSECURITY_ATTRIBUTES lpThreadAttributes,
- __in SIZE_T dwStackSize,
- __in LPTHREAD_START_ROUTINE lpStartAddress,
- __in_opt LPVOID lpParameter,
- __in DWORD dwCreationFlags,
- __out_opt LPDWORD lpThreadId
- );
第一個參數是安全屬性,指向一個LPSECURITY_ATTRIBUTES類型的結構體,一般設為NULL;
第二個參數是線程的堆棧大小,如果不是內存特別緊張的話,就設為0,表示windows將動態調整堆棧的大小;
第三個參數是指向線程函數的指針,其實就是函數名。函數名隨便起,但是在聲名函數時必須要遵守形式
- DWORD WINAPI ThreadProc(LPVOID lpParameter)
否則就無法成功調用;
第四個參數是向線程函數傳遞的參數,不傳遞時就設為NULL;
第五個參數是線程標志,有兩個可取值:
1). CREATE_SUSPENDED,表示創建後立即掛起
2). 0,表示正常創建,創建後立刻運行
第六個參數用來保存新建線程的ID,如果不需要處理線程ID的話,則可傳入NULL。
返回值是線程的句柄。
運行時效果如下
這個程序其實是有風險的,風險有二:
1). 在MFC程序中,應該盡量使用AfxBeginThread方法來創建線程。
2). 如果我不停地按start,一會內存就用光了=。=
2的解決方法就不上了,無非是使用標志位,線程沒跑完之前不再創建新的線程。
來說說AfxBeginThread。這是MFC中的比較安全的線程創建方法。函數原型如下:
- CWinThread* AfxBeginThread(
- AFX_THREADPROC pfnThreadProc,
- LPVOID pParam,
- int nPriority = THREAD_PRIORITY_NORMAL,
- UINT nStackSize = 0,
- DWORD dwCreateFlags = 0,
- LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL
- );
- CWinThread* AfxBeginThread(
- CRuntimeClass* pThreadClass,
- int nPriority = THREAD_PRIORITY_NORMAL,
- UINT nStackSize = 0,
- DWORD dwCreateFlags = 0,
- LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL
- );
有兩個可以重載的函數,常用的是第一個。也能看出來,第一個函數與CreateThread()的參數其實是差不多的,只不過順序不太一樣。需要注意的是第二個重載函數,參數一是CRuntimeClass * pThreadClass,CRuntimeClass是個結構體,MSDN裡的解釋是“The RUNTIME_CLASS of an object derived from CWinThread.”為此我特意看了一下AfxBeginThread的源代碼,其中有如下一行:
- ASSERT(pThreadClass->IsDerivedFrom(RUNTIME_CLASS(CWinThread)));
表明RUNTIME_CLASS是個宏定義。
- #define RUNTIME_CLASS(class_name) (class_name::GetThisClass())
也就是用這個宏將線程類指針轉換為指向CRuntimeClass的對象指針。
那麼新的線程創建語句就變為了:
- CWinThread * m_thread; // m_thread為成員變量
- m_thread = AfxBeginThread(ThreadProc, this);
而且需要將線程函數的聲明修改一下:
- UINT ThreadProc(LPVOID lpParameter)
線程執行的中間是可以暫停的,使用DWORD CWinThread::SuspendThread()函數即可。暫停後可以使用DWORD CWinThread::ResumeThread()函數使線程恢復運行。
這是基本用法,至於一些高級點兒的東西,明兒繼續。
PS:正所謂懂得越多就發現懂得越少,今天搜資料,又搜出好多沒聽過的東西=。=,全部記在本子上,逐個消滅之。。
PSS:下一個目標,看明白與這個網頁相關聯的東西。。http://en.wikipedia.org/wiki/Thread_(computing)
PSSS:這玩意兒真形象。。
本文出自 “正面旺得福反面泰瑞寶” 博客,請務必保留此出處http://serious.blog.51cto.com/242085/857669