易语言多线程封装线程启动返回句柄
封裝多線程模塊-線程啟動
? 1、CreateThread
? 2、線程_啟動_句柄()
? 3、線程_啟動_邏輯()
? 4、線程句柄
? 5、線程ID
? 6、易語言SHCreateThread
511遇見易語言多線程大漠多線程
SHCreateThread function | Microsoft Docs https://docs.microsoft.com/zh-cn/windows/desktop/api/shlwapi/nf-shlwapi-shcreatethread
SHCreateThread
指向包含初始化數據的可選應用程序定義數據結構的指針。它被傳遞給pfnThreadProc和pfnCallback(可選)指向的函數。這個值可以是NULL。
.參數 標志, 整數型, , dwFlags------0
控制函數行為的標志。一個或多個CTF常數。
.參數 返回信息, 整數型, , pfnCallback—0
SHCreateThread將等待pfnCallback所指向的函數返回,然后再返回給它的調用者。pfnCallback指向的函數的返回值被忽略。
這個函數,這是shlwapi.dll 的一個導出函數,這個函數看字面也能理解是個創建多線程的函數,而且。。。他是基于對象操作的多線程函數,相對比較穩定
API- Create Thread
CreateThread是一種微軟在Windows API中提供了建立新的線程的函數,該函數在主線程的基礎上創建一個新線程。線程終止運行后,線程對象仍然在系統中,必須通過CloseHandle函數來關閉該線程對象。
當某個程序創建一個線程后,會產生一個線程的句柄,線程的句柄主要用來控制整個線程的運行,例如停止、掛起或設置線程的優先級等操
第一個參數表示線程內核對象的安全屬性,一般傳入NULL表示使用默認設置。是指向SECURITY_ATTRIBUTES型態的結構的指針。在Windows 98中忽略該參數。在Windows NT中,它被設為NULL。//線程安全屬性
第二個參數表示線程棧空間大小。傳入0表示使用默認大小(1MB)。// 堆棧大小
第三個參數表示新線程所執行的線程函數地址,多個線程可以使用同一個函數地址。// 線程函數
第四個參數是傳給線程函數ThreadProc的參數。這樣主線程和從屬線程就可以共享數據。 //線程參數
第五個參數指定額外的標志來控制線程的創建,為0表示線程創建之后立即就可以進行調度,如果為CREATE_SUSPENDED則表示線程創建后暫停運行,這樣它就無法調度,直到調用ResumeThread()。// 線程創建屬性
第六個參數將返回線程的ID號,傳入NULL表示不需要返回該線程ID號。// 線程ID
函數返回值:
成功返回新線程的句柄,失敗返回0。
易語言模塊API視頻教程線程啟動CreateThread
CreateThread使用(六個參數介紹)
CreateThread function | Microsoft Docs https://docs.microsoft.com/zh-cn/windows/desktop/api/processthreadsapi/nf-processthreadsapi-createthread
1、返回值:返回線程句柄
"句柄" 類似指針, 但通過指針可讀寫對象, 通過句柄只是使用對象;
有句柄的對象一般都是系統級別的對象(或叫內核對象); 之所以給我們的是句柄而不是指針, 目的只有一個: "安全";
貌似通過句柄能做很多事情, 但一般把句柄提交到某個函數(一般是系統函數)后, 我們也就到此為止很難了解更多了; 事實上是系統并不相信我們.不管是指針還是句柄, 都不過是內存中的一小塊數據(一般用結構描述), 微軟并沒有公開句柄的結構細節, 猜一下它應該包括: 真實的指針地址、訪問權限設置、引用計數等等.既然 CreateThread 可以返回一個句柄, 說明線程屬于 "內核對象".
實際上不管線程屬于哪個進程, 它們在系統的懷抱中是平等的; 在優先級(后面詳談)相同的情況下, 系統會在相同的時間間隔內來運行一下每個線程, 不過這個間隔很小很小, 以至于讓我們誤以為程序是在不間斷地運行.這時你應該有一個疑問: 系統在去執行其他線程的時候, 是怎么記住前一個線程的數據狀態的?
有這樣一個結構 TContext, 它基本上是一個 CPU 寄存器的集合, 線程是數據就是通過這個結構切換的, 我們也可以通過 GetThreadContext 函數讀取寄存器看看.
附上這個結構 TContext(或叫: CONTEXT、_CONTEXT) 的定義:
PContext = ^TContext;
_CONTEXT = record
ContextFlags: DWORD;
Dr0: DWORD;
Dr1: DWORD;
Dr2: DWORD;
Dr3: DWORD;
Dr6: DWORD;
Dr7: DWORD;
FloatSave: TFloatingSaveArea;
SegGs: DWORD;
SegFs: DWORD;
SegEs: DWORD;
SegDs: DWORD;
Edi: DWORD;
Esi: DWORD;
Ebx: DWORD;
Edx: DWORD;
Ecx: DWORD;
Eax: DWORD;
Ebp: DWORD;
Eip: DWORD;
SegCs: DWORD;
EFlags: DWORD;
Esp: DWORD;
SegSs: DWORD;
end;
2、參數6:輸出線程ID
CreateThread 的最后一個參數是 "線程的 ID";
既然可以返回句柄, 為什么還要輸出這個 ID? 現在我知道的是:
1、線程的 ID 是唯一的; 而句柄可能不只一個, 譬如可以用 GetCurrentThread 獲取一個偽句柄、可以用 DuplicateHandle 復制一個句柄等等.
2、ID 比句柄更輕便.
在主線程中 GetCurrentThreadId、MainThreadID獲取的都是主線程的 ID.
MainInstance: Indicates the instance handle for the main executable.
Use MainInstance to obtain the instance handle for the main executable of an application. This is useful in applications that use runtime libraries or packages, when you need the handle for the executable rather than for the library.
3、參數5:啟動選項
CreateThread 的倒數第二個參數 dwCreationFlags(啟動選項) 有兩個可選值:
0: 線程建立后立即執行入口函數;
CREATE_SUSPENDED: 線程建立后會掛起等待.
ResumeThread 恢復線程的運行; SuspendThread 掛起線程.
這兩個函數的參數都是線程句柄, 返回值是執行前的掛起計數.
什么是掛起計數?
SuspendThread 會給這個數 +1; ResumeThread 會給這個數 -1; 但這個數最小是 0.
當這個數 = 0 時, 線程會運行; > 0 時會掛起.
如果被 SuspendThread 多次, 同樣需要 ResumeThread 多次才能恢復線程的運行.
ResumeThread 和 SuspendThread 分別對應 TThread 的 Resume 和 Suspend 方法, 很好理解.
4、參數4:函數參數
線程入口函數的參數是個無類型指針(Pointer), 用它可以指定任何數據; 本例是把鼠標點擊窗體的坐標傳遞給線程的入口函數, 每次點擊窗體都會創建一個線程.
5、參數3:入口函數指針
到了入口函數了, 學到這個地方, 我查了一個入口函數的標準定義, 這個函數的標準返回值應該是 DWORD, 不過這函數在 Delphi 的 System 單元定義的是: TThreadFunc = function(Parameter: Pointer): Integer; 我以后會盡量使用 DWORD 做入口函數的返回值.
這個返回值有什么用呢?
等線程退出后, 我們用 GetExitCodeThread 函數獲取的退出碼就是這個返回值!
如果線程沒有退出, GetExitCodeThread 獲取的退出碼將是一個常量 STILL_ACTIVE (259); 這樣我們就可以通過退出碼來判斷線程是否已退出.
還有一個問題: 前面也提到過, 線程函數不能是某個類的方法! 假如我們非要線程去執行類中的一個方法能否實現呢?
盡管可以用 Addr(類名.方法名) 或 MethodAddress('published 區的方法名') 獲取類中方法的地址, 但都不能當做線程的入口函數, 原因可能是因為類中的方法的地址是在實例化為對象時動態分配的.
后來換了個思路, 其實很簡單: 在線程函數中再調用方法不就得了, 估計 TThread 也應該是這樣.
CreateThread 第三個參數是函數指針, 新線程建立后將立即執行該函數, 函數執行完畢, 系統將銷毀此線程從而結束多線程的故事.
6、參數2:堆棧大小
棧是私有的但堆是公用的
CreateThread 的第二個參數是分配給線程的堆棧大小.
這首先這可以讓我們知道: 每個線程都有自己獨立的堆棧(也擁有自己的消息隊列).
什么是堆棧? 其實堆是堆、棧是棧, 有時 "棧" 也被叫做 "堆棧".
它們都是進程中的內存區域, 主要是存取方式不同(棧:先進后出; 堆:先進先出);
"棧"(或叫堆棧)適合存取臨時而輕便的變量, 主要用來儲存局部變量; 譬如 for i := 0 to 99 do 中的 i 就只能存于棧中, 你把一個全局的變量用于 for 循環計數是不可以的.
現在我們知道了線程有自己的 "棧", 并且在建立線程時可以分配棧的大小.
前面所有的例子中, 這個值都是 0, 這表示使用系統默認的大小, 默認和主線程棧的大小一樣, 如果不夠用會自動增長;
那主線程的棧有多大? 這個值是可以設定的: Project -> Options -> Delphi Compiler -> Linking(如圖)
棧是私有的但堆是公用的, 如果不同的線程都來使用一個全局變量有點亂套;
為解決這個問題 Delphi 為我們提供了一個類似 var 的 ThreadVar 關鍵字, 線程在使用 ThreadVar 聲明的全局變量時會在各自的棧中留一個副本, 這樣就解決了沖突. 不過還是盡量使用局部變量, 或者在繼承 TThread 時使用類的成員變量, 因為 ThreadVar 的效率不好, 據說比局部變量能慢 10 倍.
7、參數1:安全設置
CreateThread 的第一個參數 lpThreadAttributes 是指向 TSecurityAttributes 結構的指針, 一般都是置為 nil, 這表示沒有訪問限制; 該結構的定義是:
//TSecurityAttributes(又名: SECURITY_ATTRIBUTES、_SECURITY_ATTRIBUTES)
_SECURITY_ATTRIBUTES = record
nLength: DWORD; {結構大小}
lpSecurityDescriptor: Pointer; {默認 nil; 這是另一個結構 TSecurityDescriptor 的指針}
bInheritHandle: BOOL; {默認 False, 表示不可繼承}
end;
//TSecurityDescriptor(又名: SECURITY_DESCRIPTOR、_SECURITY_DESCRIPTOR)
_SECURITY_DESCRIPTOR = record
Revision: Byte;
Sbz1: Byte;
Control: SECURITY_DESCRIPTOR_CONTROL;
Owner: PSID;
Group: PSID;
Sacl: PACL;
Dacl: PACL;
end;
夠復雜的, 但我們在多線程編程時不需要去設置它們, 大都是使用默認設置(也就是賦值為 nil).我覺得有必要在此刻了解的是: 建立系統內核對象時一般都有這個屬性(TSecurityAttributes);
在接下來多線程的課題中要使用一些內核對象, 不如先盤點一下, 到時碰到這個屬性時給個 nil 即可, 不必再費神.
{建立事件}
function CreateEvent(
lpEventAttributes: PSecurityAttributes; {!}
bManualReset: BOOL;
bInitialState: BOOL;
lpName: PWideChar
): THandle; stdcall;
{建立互斥}
function CreateMutex(
lpMutexAttributes: PSecurityAttributes; {!}
bInitialOwner: BOOL;
lpName: PWideChar
): THandle; stdcall;
{建立信號}
function CreateSemaphore(
lpSemaphoreAttributes: PSecurityAttributes; {!}
lInitialCount: Longint;
lMaximumCount: Longint;
lpName: PWideChar
): THandle; stdcall;
{建立等待計時器}
function CreateWaitableTimer(
lpTimerAttributes: PSecurityAttributes; {!}
bManualReset: BOOL;
lpTimerName: PWideChar
): THandle; stdcall;上面的四個系統內核對象(事件、互斥、信號、計時器)都是線程同步的手段, 從這也能看出處理線程同步的復雜性; 不過這還不是全部, Windows Vista 開始又增加了 Condition variables(條件變量)、Slim Reader-Writer Locks(讀寫鎖)等同步手段.不過最簡單、最輕便(速度最快)的同步手段還是 CriticalSection(臨界區), 但它不屬于系統內核對象, 當然也就沒有句柄、沒有 TSecurityAttributes 這個安全屬性, 這也導致它不能跨進程使用; 不過寫多線程時一般不用跨進程啊, 所以 CriticalSection 應該是最常用的同步手段.
原文鏈接:http://www.cnblogs.com/del/category/174761.html
源碼:
.版本 2 .支持庫 EThread.子程序 _按鈕1_被單擊編輯框4.內容 = 到文本 (啟動線程 (&子程序1, , e_hwnd)) 編輯框1.內容 = 到文本 (e_hwnd).子程序 子程序1 .局部變量 i.判斷循環首 (e_是否啟動 = 假) i = i + 1 列表框1.加入項目 (到文本 (i), ) 標簽1.標題 = 到文本 (列表框1.取項目數 ()) 延時 (1) 處理事件 () .判斷循環尾 ().子程序 _按鈕2_被單擊a_hwnd = 線程_啟動_句柄 (&子程序2, , a_thid) 編輯框2.內容 = 到文本 (a_hwnd) 編輯框3.內容 = 到文本 (a_thid).子程序 子程序2 .局部變量 n.判斷循環首 (a_是否啟動 = 假) n = n + 1 列表框2.加入項目 (到文本 (n), ) 標簽2.標題 = 到文本 (列表框2.取項目數 ()) 延時 (1) 處理事件 () .判斷循環尾 ().子程序 _按鈕3_被單擊e_是否啟動 = 真 線程_關閉句柄 (e_hwnd).子程序 _按鈕4_被單擊a_是否啟動 = 真 線程_關閉句柄 (a_hwnd)模塊源碼:
.版本 2 .程序集 集_多線程.子程序 線程_啟動_句柄, 整數型, 公開, 成功返回線程句柄,失敗返回0 .參數 要啟動的子程序, 子程序指針, , 要啟動的子程序 .參數 要傳去子程序的參數, 整數型, 可空, 往子程序傳遞一個整數型參數 .參數 線程ID, 整數型, 參考 可空, 整數型的變量,用于存儲新創建的線程ID返回 (CreateThread (0, 0, 要啟動的子程序, 要傳去子程序的參數, 0, 線程ID)).子程序 線程_關閉句柄, 邏輯型, 公開, 關閉一個線程,關閉后線程繼續運行,但是無法對這個線程操作 .參數 線程句柄, 整數型返回 (CloseHandle (線程句柄))源碼:511遇見易語言大漠多線程系列免費視頻教程
總結
以上是生活随笔為你收集整理的易语言多线程封装线程启动返回句柄的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: js百度地图计算点与折线最短距离
- 下一篇: js简单表单验证(弹出框)