文章目錄
- 線程簡介
- 線程與函數
- 核心對象
- 進程與核心對象
- 線程與進程
線程簡介
我們知道一般情況程式中的代碼都是按順序從頭開始一行一行的執行以最後.中間不能出現同時執行的情況.比如一段代碼調用兩個函數
FunOne();
FunTwo();
只要當函數FunOne中的代碼執行完才返回來執行FunTwo.假如邏輯上是有先後順序那還真只能這樣按順序執行下來.不過有假如FunOne與FunTwo沒有邏輯先後順序,是相互獨立的.比如兩個函數分別處理兩不同的檔案one.text與two.txt.
這種情形就可以用到線程,弄兩個線程去執行這兩函數.這樣兩函數同時執行,提高了效率(如果單核的CPU可能沒有真正的並行效果不明顯,那多核CPU執行多線程那是能夠真正達到並存執行,效果很明顯的).
實際上可以這樣簡單的理解線程,它是CPU的調度單位.而一個線程是對應一個函數.所以別把一個線程想得太複雜,就只是執行個函數而已.只不過執行的時候是並存執行罷了.如果只是簡單的幾個線程不涉及使用共同的資源,沒其他啥關聯.就完全跟簡單的執行一個函數類似.只是如果多個線程間關係複雜就會涉及到啥同步問題,那樣就有很多複雜的細節性問題.
線程與函數
線程函數必須是全域函數,或者是類的靜態成員函數,因為非靜態成員函數有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,當然了前提是進程要有對那個控制代碼的存取權限先.(在控制代碼表中有)
線程與進程
進程只是個容器,不會執行任何操作.它裡面有很多線程(至少必須有一個主線程).進程內的所有線程共用進程的核心對象.
當一個進程中止時所以線程自然中止.