VC6.0 線程編程

線程

線程是“進程”中某個單一順序的控制流。也稱爲輕量進程(lightweight process--LWP)。指運行中的程序的調度單位。一個標準的線程由線程ID,當前指令指針(PC),寄存器集合和堆棧組成。另外,線程是進程中的一個實體,是被系統獨立調度和分派的基本單位,線程自己不擁有系統資源,只擁有一點在運行中必不可少的資源,但它可與同屬一個進程的其他線程共享進程所擁有的全部資源。一個線程可以創建和撤銷另一個線程,同一進程中的多個線程之間可以併發執行。由於線程之間的相互制約,致使線程在運行中呈現出間斷性。線程也有就緒、阻塞和運行三種基本狀態。每一個程序都至少有一個線程,若程序只有一個線程,那就是程序本身。

線程是程序中一個單衣的順序控制流程。在單個程序中同時運行多個線程完成不同的工作,成爲多線程。


Windows Thread

頭文件:#include<process.h>//for _beginthread

需要設置;Project-->>Setting-->>C/C++-->>Code Generation-->>User run-time library 選擇Debug Multithreaded 或者Multithreaded。也可在Project Option中加入/MT 或/MTD


經典例子:

// Thread.cpp
/************************************************************************/
/* 
 主線程創建2個線程t1和t2,創建時,2個線程就被掛起,後來調用ResumeThread
 恢復2個線程,是其開始執行,調用WaitForSingleObject等待2個線程執行完,然後
 退出主線程,即結束進程。
*/
/************************************************************************/
/************************************************************************/
/* 
   One hassle  is that fact that C++ must employ a free(C) function
or a static class member function as the thread entry function.
	This program must be compiled with a multi-threaded C run-time
(/MT for LIBCMT.LIB in a release build or /MTd for LIBCMTD.LIB in 
a debug build).
*/
/************************************************************************/
#include 
#include              // for STL string class
#include           // for HANDLE
#include           // for _beginthread()
using namespace std;

class ThreadX
{
private:
  int loopStart;
  int loopEnd;
  int dispFrequency;
public:
  string threadName;

  ThreadX( int startValue, int endValue, int frequency )
  {
	loopStart = startValue;
	loopEnd = endValue;
	dispFrequency = frequency;
  }
/************************************************************************/
/* 
 In C++ you must employ a free(C) function or a static class member
 function as the thread entry-point-function. Furthermore, _beginthreadex()
 demands that the thread entry function signature take a single(void *) and
 returned an unsigned.
  */
/************************************************************************/
  static unsigned __stdcall ThreadStaticEntryPoint(void * pThis)
  {
	  ThreadX * pthX = (ThreadX*)pThis;   // the tricky cast
	  pthX->ThreadEntryPoint();           // now call the true entry-point-function
	  /*
		A thread terminates automatically if it completes execution,
		or it can terminate itself with a call to _endthread().
	  */
	  return 1;						      // the thread exit code
  }

  void ThreadEntryPoint()
  {
	  /************************************************************************/
	  /* 
		This is the desired entry-point-function but to get here we have 
		to use a 2 step procedure involving the ThreadStaticEntryPoint()
		function.
	  */
	  /************************************************************************/
	for (int i = loopStart; i <= loopEnd; ++i)
	{
	  if (i % dispFrequency == 0)
	  {
		  printf( "%s: i = %d\n", threadName.c_str(), i );
	  }
	}
	printf( "%s thread terminating\n", threadName.c_str() );
  }
};


int main()
{
	/************************************************************************/
	/* 
		All processes get a primary thread automatically. This primary
		thread can generate additional threads. In this program the primary
		thread creates 2 additional threads and all 3 threads then run 
		simultaneously without any synchronization. No data is shared 
		between	the threads.

		We instantiate an object of the ThreadX class. Next we will 
		create a thread and specify that the thread is to begin executing
		the function ThreadEntryPoint() on object o1. Once started,
		this thread will execute until that function terminates or until
		the overall process terminates.
	*/
	/************************************************************************/
    ThreadX * o1 = new ThreadX( 0, 1, 2000 );
	/************************************************************************/
	/* 
	When developing a multithreaded WIN32-based application with
	Visual C__, you need to use the CRT thread functions to create any
	threads that call CRT functions. Hence to create and terminate
	threads, use _begintreadex() and _endthreadex() instead of the Win32
	APIs CreateThread() and EndThread().

	The multithread library LIBCMT.LIB includes the _beginthread()
	and _endthread() functions. The _beginthread() function performs
	initialization without which many C run-time functions will fail.
	You must use _beginthread() instead of CreateThread() in C programs
	built with LIBCMT.LIB if you intend to call C run-time functions.

	Unlike the thread handle returned by _beginthread(), the thread handle
	returned by _beginthreadex() can be used with the synchronization APIs.
	*/
	/************************************************************************/
    HANDLE   hth1;
    unsigned  uiThread1ID;

    hth1 = (HANDLE)_beginthreadex( NULL,         // security
                                   0,            // stack size
                                   ThreadX::ThreadStaticEntryPoint,
                                   o1,           // arg list
                                   CREATE_SUSPENDED,  // so we can later call ResumeThread()
                                   &uiThread1ID );

    if ( hth1 == 0 )
        printf("Failed to create thread 1\n");

    DWORD   dwExitCode;
    GetExitCodeThread( hth1, &dwExitCode );  // should be STILL_ACTIVE = 0x00000103 = 259
    printf( "initial thread 1 exit code = %u\n", dwExitCode );

	/************************************************************************/
	/* 
		The System::Threading::Thread object in C++/CLI has a "Name" property.
		To create the equivalent functionality in C++ I added a public data number 
		named threadName.
	*/
	/************************************************************************/
    o1->threadName = "t1";

    ThreadX * o2 = new ThreadX( -100000, 0, 2000 );

    HANDLE   hth2;
    unsigned  uiThread2ID;

    hth2 = (HANDLE)_beginthreadex( NULL,         // security
                                   0,            // stack size
                                   ThreadX::ThreadStaticEntryPoint,
                                   o2,           // arg list
                                   CREATE_SUSPENDED,  // so we can later call ResumeThread()
                                   &uiThread2ID );

    if ( hth2 == 0 )
        printf("Failed to create thread 2\n");

    GetExitCodeThread( hth2, &dwExitCode );  // should be STILL_ACTIVE = 0x00000103 = 259
    printf( "initial thread 2 exit code = %u\n", dwExitCode );

    o2->threadName = "t2";
/************************************************************************/
/* 
	If we hadn't specifed CREATE_SUSPENDED in the call to
	_beginthreadex(), we wouldn't now need to call ResumeThread()
	*/
/************************************************************************/
    ResumeThread( hth1 );   // serves the purpose of Jaeschke's t1->Start()
    ResumeThread( hth2 );  //你需要恢復線程的句柄,使用該函數能夠激活線程運行
	
	/************************************************************************/
	/* 
		In C++/CLI the process continues until the last thread exits.
		That is, the thread's have independent lifetimes. Hence
		Jaeschke's original code was designed to show that the primary
		thread could exit and not influence the other threads.

		However, in C++ the process terminates when the primary thread exits
		and when the process terminates all its threads are then terminated.
		Hence if you comment out the following waits, the non-primary threads will
		never get a chance to run.
	*/
	/************************************************************************/

    WaitForSingleObject( hth1, INFINITE );
    WaitForSingleObject( hth2, INFINITE );

	/************************************************************************/
	/* 
		WaitForSingleObject 函數用來檢測 hHandle 事件的信號狀態,
		當函數的執行時間超過 dwMilliseconds 就返回,但如果參數
		dwMilliseconds 爲INFINITE時,函數將指導相應時間事件變成
		有信號狀態才返回,否則就一直等待下去。直到,WaitForSingleObject
		有返回才執行後面代碼。
	*/
	/************************************************************************/
    GetExitCodeThread( hth1, &dwExitCode );
    printf( "thread 1 exited with code %u\n", dwExitCode );

    GetExitCodeThread( hth2, &dwExitCode );
	/************************************************************************/
	/* 
		GetExitCodeThread這個函數是獲得線程的退出碼,第二個參數
		是一個DWORD的指針,用戶應該使用一個DWORD類型的變量
		去接收數據,返回的數據是線程的退出碼,第一個參數是線程句柄
		,用 CreateThread 創建線程時,獲得到。
		通過線程退出碼可以判斷線程是否真在運行,還是已經退出。
	*/
	/************************************************************************/
    printf( "thread 2 exited with code %u\n", dwExitCode );

    CloseHandle( hth1 );
    CloseHandle( hth2 );

    delete o1;
    o1 = NULL;

    delete o2;
    o2 = NULL;

    printf("Primary thread terminating.\n");
	return 0;
}


NOTE:

1. 如果你在編寫C/C++代碼,決不應該調用CreateThread。相反,應該使用Visual C++運行時庫函數_beginthreadex,退出也應該使用_endthreadex。如果不使用Microsoft的Visual C++編譯器,你的編譯器供應商有它自己的CreateThread替代函數。

2.因爲_beginthreadex和_endthreadex是CRT(C Runtime Library)線程函數,所以必須注意編譯選項runtime library的選擇,使用MT或MTD 【MultiThreaded, Debug MultiThreaded】。

3._beginthreadex 函數的參數列表與CreateThread函數的參數列表是相同的, 但是參數名和類型並不完全相同。這是因爲Microsoft的C/C++運行期庫的開發小組認爲,C/C++運行時函數不應該對Windows數據類型有任何依賴。_beginthreadex函數也像CreateThread那樣,返回新創建的線程的句柄。


關於_beginthreadex的一些要點:

(1).每個線程均獲得由C/C++運行時庫的堆棧分配的自己的tiddata內存結構。(tiddata結構位於Mtdll.h文件中的Visual C++ 源代碼中)。

(2).傳遞給_beginthreadex的線程函數的地址保存在tiddata內存塊中。傳遞給該函數的參數也保存在該數據塊中。

(3)._beginthreadex確實從內部調用CreateThread,因爲這是操作系統瞭解如何創建新線程的唯一方法。

(4).當調用CreateThread時,它被告知通過調用_threadstartex而不是pfnStartAddr來啓動執行新線程。還有就是,傳遞給線程函數的參數是tiddata結構而不是pvParam的地址。

(5).如果一切順利,就會像CreateThread那樣返回線程句柄。如果任何操作失敗了,便返回NULL。


_endthreadex的一些要點

C運行時庫的_getptd函數內部調用操作系統的TlsGetValue函數,該函數負責檢索調用線程的tiddata內存塊的地址。然後該數據塊被釋放,而操作系統的ExitThread函數被調用,以便真正撤銷該線程。當然,退出代碼要正確地設置和傳遞。


總結

雖然也提供了簡化版的_beginthread和_endthread,但是可控制性太過粗糙,所以一般不使用。

線程handle因爲是內核對象,所以需要在最後closehandle。

更豐富的API

HANDLE GetCurrentProcess();

HANDLE GetCurrentThread();

DWORD GetCurrentProcessId();

DWORDGetCurrentThreadId();

DWORDSetThreadIdealProcessor(HANDLE hThread, DWORD dwldealProcessor);

BOOL SetPriorityClass(GetCurrentProcess(), IDLE_PRIORITY_CLASS);

BOOL SetThreadPriority(HANDLE hTread, in nPriotiry);

BOOL GetThreadContext(HANDLE hThread, PCONTEXT pContext);

BOOL SwitchToThread();


重點關注:

(1).C++主線程的終止,同時也會終止所有主線程創建的子線程,不管子線程有沒有執行完畢。所以代碼中,如果不調用WaitForSingleObject,則2個子線程t1和t2可能並沒有執行完畢或根本就沒有執行。

(2).如果某線程掛起,然後有調用WaitForSingleObject等待該線程,就會導致死鎖。所以代碼中,不調用resumethread,則會死鎖。


_beginthreadex OR CreateThread & Why

爲什麼要用C運行時庫的_beginthreadex代替操作系統的CreateThread來創建線程?

來源自1999年7月MSJ雜誌的《Win32 Q&A》欄目

你也許會說:“我一直用CreateThread來創建線程,一直都工作得好好的,爲什麼要用_beginthreadex來代替CreateThread。”下面讓我來告訴你爲什麼。

回答一個問題可以有兩種方式,一種是簡單的,一種是複雜的。

如果你不願意看下面的長篇大論,那我可以告訴你簡單的答案:_beginthreadex在內部調用了CreateThread,在調用之前_beginthreadex做了很多的工作,從而使得它比CreateThread更安全。

發佈了42 篇原創文章 · 獲贊 1 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章