摘要:本文主要以mfc線程為主題展開的概述,詳細介紹了線程,線程與函數以及實例來說明,下面我們一起來看看原文。
線程簡介
我們知道一般情況程序中的代碼都是按順序從頭開始一行一行的執行以最后。中間不能出現同時執行的情況。比如一段代碼調用兩個函數
FunOne();
FunTwo();
只要當函數FunOne中的代碼執行完才返回來執行FunTwo.假如邏輯上是有先后順序那還真只能這樣按順序執行下來。不過有假如FunOne與FunTwo沒有邏輯先后順序,是相互獨立的。比如兩個函數分別處理兩不同的文件one.text與two.txt.
這種情形就可以用到線程,弄兩個線程去執行這兩函數。這樣兩函數同時執行,提高了效率(如果單核的CPU可能沒有真正的并行效果不明顯,那多核CPU執行多線程那是能夠真正達到并行執行,效果很明顯的)。
實際上可以這樣簡單的理解線程,它是CPU的調度單位。而一個線程是對應一個函數。所以別把一個線程想得太復雜,就只是執行個函數而已。只不過執行的時候是并行執行罷了。如果只是簡單的幾個線程不涉及使用共同的資源,沒其他啥關聯。就完全跟簡單的執行一個函數類似。只是如果多個線程間關系復雜就會涉及到啥同步問題,那樣就有很多復雜的細節性問題。
線程間通信的方式
方式一:全局變量,各線程通過修改全局變量傳遞信息,通過循環檢測查看信息。 方式二:消息傳遞,通過windows消息機制傳遞。
方式一問題主要有兩個:第一是全局變量過多,不便于管理;第二個是循環檢測過于緩慢,并且較不靈活,不能夠迅速響應變化。
MFC中存在三種線程
A、 Ui線程:通過創建窗口得到,具備標準窗口的功能。
B、 工作者線程:通過CreateThread函數直接創建,不具備消息隊列。
C、 帶消息隊列的工作者線程:通過繼承CWinThread得到,具備消息隊列。
A種和C種線程使用消息隊列,對于使用者來說,這兩種線程需要自己本身或其他線程向其發送消息,才進行相應工作,否則保持靜默狀態,該兩種線程擅長實時處理外部信號。 B種線程不使用消息隊列,可以用單個函數作為其線程的本體,適合處理步驟相對固定的算法。
線程與函數
線程函數必須是全局函數,或者是類的靜態成員函數,因為非靜態成員函數有this指針,而在進程中無法訪問此指針。
但是靜態成員函數只能訪問靜態成員,解決此問題途徑:
1. 就是在調用靜態成員函數時將this指針作為參數傳入,通過該指針訪問非靜態成員。
2. 不將線程函數定義為類的靜態成員函數,而是定義為類的友元函數,這樣函數線程也可以有類成員函數相同的權限。
最簡單示例
線程分工作線程與界面線程。這里就以工作線程為例
1.先來看個MFC中的創建線程的簡單例子。
UINT ThreadFun(LPVOID pParam){ //線程要調用的函數
MessageBox(NULL,_T(“i am called by a thread.”), _T(“thread func”),MB_OK);
}
::AfxBeginThread(ThreadFun, NULL); //這就是創建一個線程并執行了,調用上面的函數彈出一個對話框。
2.示例分析
上面的線程是簡單的不能再簡單了吧。下面從兩個來分析下。
a.首先是被調用的函數有啥講究不? 當然有,被線程用到的函數格式必須是統一的,返回類型必須是UINT,函數只能有一個參數LPVOID.其中UINT就是個無符號的整形,LPVOID是void*,所以這個參數表示可以傳任何類型的指針過來的。
b.函數AfxBeginThread的分析。
這個函數還有返回值CWinThread*的,如果你只是簡單的創建一個線程并執行,就不用管了。但如果想要對創建的線程做其他操作就必須這樣寫。
CWinThread* pThread = ::AfxBeginThread(ThreadFun, NULL); //接下來做啥就直接調用pThead就行。
另外函數AfxBeginThread的參數有很多個,但很多都有默認值。下面是完整的參數
CWinThread* AfxBeginThread(
AFX_THREADPROC pfnThreadProc, //一個函數指針
LPVOID pParam, //void*類型的指針,可以傳任何種類指針過來。
int nPriority = THREAD_PRIORITY_NORMAL, //線程優先級
UNT nStackSize = 0, //分配堆棧大小
DWORD dwCreateFlags = 0, //表示線程創建后是立即執行還是等會執行
LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL //線程安全屬性指針
);//用于創建工作者線程
上面的參數我們用的最多的是3個,其他一盤都默認值。
AFX_THREADPROC pfnThreadProc //函數指針肯定是必須要指定的,不然線程執行哪個函數去啊
LPVOID pParam //這是傳給上面指定函數的參數。如果被調用的函數需要啥參數就只能在這里指定了。
DWORD dwCreateFlags //默認值為0表示創建線程后立即執行。如果是CREATE_SUSPEND則表示創建好后先掛起。必須通過ResumeThread來執行。
稍復雜的例子
擴充下上面的例子,給函數傳入參數,并且休眠和掛起線程。
UINT ThreadFun(LPVOID pParam){ //線程要調用的函數
int* pNum = (int*)pParam; //假如會傳入一個整形指針參數
MessageBox(NULL,_T(“i am called by a thread.”), _T(“thread func”),MB_OK);
}
CWinThread* pThread; //定義一個線程指針
void CreateThread(){//創建一個線程并掛起
int* pNum = new int(88); //傳入的參數
pThread = ::AfxBeginThread(ThreadFun, pNum,THREAD_PRIORITY_NORMAL,0,CREATE_SUSPENDED);
}
void StartThread(){ //運行線程
pThread-》ResumeThread();
}
線程休眠與掛起的區別
上面示例中先創建一個線程,并讓它掛起。(suspend),被掛起的線程只有通過ResumeThread才能開始執行。
而休眠則一般是這樣使用。
UINT ThreadFun(LPVOID pParam){ //線程要調用的函數
::Sleep(1000); //表示函數執行到這里先休息1000微秒,也就是1秒。然后再接著執行下面的語句。
MessageBox(NULL,_T(“i am called by a thread.”), _T(“thread func”),MB_OK);
}
所以休眠一般是會在線程調用的那個函數中指定。休眠在指定的時間后會自動再次執行,相當于暫停一斷時間然后又自動活過來了,不用像掛起還必須得顯式去啟動才行。
內核對象與進程
內核對象
這里的對象不不是指一個類的實例化,不過實際上也可以類似的等同。因為內核對象是內核分配的一個內存塊,這種內存塊就是一個結構體(struct)。應用程序若需要訪問內核對象需要通過一些API函數,不能直接訪問(基于安全的考慮)。內核對象的擁有者是內核,所以何時釋放對象的內存是由內核決定的。我們使用內核對象時一般是通過一個句柄支間接的使用,于是每有一個句柄與對象關聯則對象的引用計數加1,當系統發現內核對象的引用計數為0時則釋放內核對象內存。(看起來是不是有點像智能指針的用法了啊?)
進程與內核對象
每個進程在初始化時被分配一個句柄表,表中保存進程能訪問的所有內核對象的句柄(進程是不能直接訪問內核對象,只能先在找到句柄表中的句柄,然后再使用內核對象。)
當然進程還能通過CreateObject來創建一些內核對象,然后不使用時使用CloseHandle來關閉內核對象。
如果某個進程創建內核對象時指定SECURITY_ATTRIBUTES中的bInheritHandle為TRUE,創建子進程時(CreateProcess也設bInheritHandle為TRUE)則子進程也能擁有那個內核對象的訪問權限(此時子進程的句柄表會復制該內核對象句柄過來,內核對象引用計數加1)。當然如果父進程在創建了子進程之后再生成一些內核對象,則子進程是不會繼承那訪問權限的。
除了通過繼承可以獲得某個內核對象的訪問權限外還可以通過同名共享(不過需要內核對象支持這種共享方式,不是所有種類的內核對象支持)。當然通過CreateObject來創建好一個名為test1的內核對象后,此時如果有另外的進程再創建一個名為test1的內核對象那不會真的創建,而只是返回之前已創建好的test的句柄(看起來有點像是單例模式的應用啊)
另外還可以通過復制內核對象的句柄,通過DuplicateHandle,當然了前提是進程要有對那個句柄的訪問權限先。(在句柄表中有)
線程與進程
進程只是個容器,不會執行任何操作。它里面有很多線程(至少必須有一個主線程)。進程內的所有線程共享進程的內核對象。
當一個進程中止時所以線程自然中止。
評論
查看更多