天天看點

線程線程

線程

一、簡介

1.定義

線程,有時被稱為輕量級程序(Lightweight Process,LWP),是程式執行流的最小單元。一個标準的線程由線程ID,目前指令指針(PC),寄存器集合和堆棧組成。

另外,線程是程序中的一個實體,是被系統獨立排程和分派的基本機關,線程自己不擁有系統資源,隻擁有一點兒在運作中必不可少的資源,但它可與同屬一個程序的其它線程共享程序所擁有的全部資源。一個線程可以建立和撤消另一個線程,同一程序中的多個線程之間可以并發執行。由于線程之間的互相制約,緻使線程在運作中呈現出間斷性。

線程也有就緒、阻塞和運作三種基本狀态。每一個程式都至少有一個線程,若程式隻有一個線程,那就是程式本身。線程是程式中一個單一的順序控制流程。在單個程式中同時運作多個線程完成不同的工作,稱為多線程。

2.特點

在多線程OS中,通常是在一個程序中包括多個線程,每個線程都是作為利用CPU的基本機關,是花費最小開銷的實體。線程具有以下屬性。

1)輕型實體

線程中的實體基本上不擁有系統資源,隻是有一點必不可少的、能保證獨立運作的資源,比如,在每個線程中都應具有一個用于控制線程運作的線程控制塊TCB,用于訓示被執行指令序列的程式計數器、保留局部變量、少數狀态參數和傳回位址等的一組寄存器和堆棧。

2)獨立排程和分派的基本機關

在多線程OS中,線程是能獨立運作的基本機關,因而也是獨立排程和分派的基本機關。由于線程很“輕”,故線程的切換非常迅速且開銷小(在同一程序中的)。

3)可并發執行

在一個程序中的多個線程之間,可以并發執行,甚至允許在一個程序中所有線程都能并發執行;同樣,不同程序中的線程也能并發執行,充分利用和發揮了處理機與外圍裝置并行工作的能力。

4)共享程序資源

在同一程序中的各個線程,都可以共享該程序所擁有的資源,這首先表現在:所有線程都具有相同的位址空間(程序的位址空間),這意味着,線程可以通路該位址空間的每一個虛位址;此外,還可以通路程序所擁有的已打開檔案、定時器、信号量機構等。由于同一個程序内的線程共享記憶體和檔案,是以線程之間互相通信不必調用核心。

3.線程與程序的比較

程序是資源配置設定的基本機關。所有與該程序有關的資源,都被記錄在程序控制塊PCB中。以表示該程序擁有這些資源或正在使用它們。另外,程序也是搶占處理機的排程機關,它擁有一個完整的虛拟位址空間。當程序發生排程時,不同的程序擁有不同的虛拟位址空間,而同一程序内的不同線程共享同一位址空間。與程序相對應,線程與資源配置設定無關,它屬于某一個程序,并與程序内的其他線程一起共享程序的資源。

線程隻由相關堆棧(系統棧或使用者棧)寄存器和線程控制表TCB組成。寄存器可被用來存儲線程内的局部變量,但不能存儲其他線程的相關變量。

通常在一個程序中可以包含若幹個線程,它們可以利用程序所擁有的資源。在引入線程的作業系統中,通常都是把程序作為配置設定資源的基本機關,而把線程作為獨立運作和獨立排程的基本機關。由于線程比程序更小,基本上不擁有系統資源,故對它的排程所付出的開銷就會小得多,能更高效的提高系統内多個程式間并發執行的程度,進而顯著提高系統資源的使用率和吞吐量。因而近年來推出的通用作業系統都引入了線程,以便進一步提高系統的并發性,并把它視為現代作業系統的一個重要名額。

線程與程序的差別可以歸納為以下4點:

1)位址空間和其它資源(如打開檔案):程序間互相獨立,同一程序的各線程間共享。某程序内的線程在其它程序不可見。

2)通信:程序間通信IPC,線程間可以直接讀寫程序資料段(如全局變量)來進行通信——需要程序同步和互斥手段的輔助,以保證資料的一緻性。

3)排程和切換:線程上下文切換比程序上下文切換要快得多。

4)在多線程OS中,程序不是一個可執行的實體。

二、線程建立函數

微軟在Windows API中提供了建立新的線程的函數CreateThread,當使用CreateProcess調用時,系統将建立一個程序和一個主線程。

CreateThread将在主線程的基礎上建立一個新線程,大緻做如下步驟: 

1在核心對象中配置設定一個線程辨別/句柄,可供管理,由CreateThread傳回 

2把線程退出碼置為STILL_ACTIVE,把線程挂起計數置1 

3配置設定context結構 

4配置設定兩頁的實體存儲以準備棧,保護頁設定為PAGE_READWRITE,第2頁設為PAGE_GUARD 

5 lpStartAddr和lpvThread值被放在棧頂,使它們成為傳送給StartOfThread的參數 

6把context結構的棧指針指向棧頂(第5步)指令指針指向startOfThread函數 

MSDN中CreateThread原型: 

HANDLE CreateThread( LPSECURITY_ATTRIBUTES lpThreadAttributes, 
  DWORD dwStackSize, 
  LPTHREAD_START_ROUTINE lpStartAddress, 
  LPVOID lpParameter, 
  DWORD dwCreationFlags, 
  LPDWORD lpThreadId); 
           

參數說明: 

lpThreadAttributes:指向 SECURITY_ATTRIBUTES 型态的結構的指針。在 Windows 98 中忽略該參數。在 Windows NT 中,它被設為 NULL ,表示使用預設值。 

dwStackSize:線程堆棧大小,一般 =0 ,在任何情況下, Windows 根據需要動态延長堆棧的大小。 

lpStartAddress:指向線程函數的指針,形式: @ 函數名,函數名稱沒有限制,但是必須以下列形式聲明: 

   DWORD WINAPI ThreadProc (LPVOID pParam) ,格式不正确将無法調用成功。 

lpParameter:向線程函數傳遞的參數,是一個指向結構的指針,不需傳遞參數時,為 NULL 。 

dwCreationFlags :線程标志 , 可取值如下 

CREATE_SUSPENDED: 建立一個挂起的線程 

  0 :建立後立即激活。 

lpThreadId:儲存新線程的id。 

傳回值:

函數成功,傳回線程句柄;函數失敗傳回false。 

函數說明:

建立一個線程。文法: 

hThread = CreateThread (&security_attributes, dwStackSize, ThreadProc,pParam, dwFlags, &idThread) ; 

  一般并不推薦使用 CreateTheard函數,而推薦使用RTL 庫裡的System單元中定義的 BeginTheard函數,因為這除了能建立一個線程和一個入口函數以外,還增加了幾項保護措施。 

三、多線程示例

#include "stdafx.h"
#include<windows.h>//需要通路Windows API函數CreateThread
#include <iostream>
using namespace std;
DWORD WINAPI Fun1Proc(//注意線程函數的聲明形式
LPVOID pParam //thread data
);
DWORD WINAPI Fun2Proc(LPVOID pParam);
int index=0;
int _tmain(int argc, _TCHAR* argv[])
{
HANDLE hThread1;
HANDLE hThread2;
hThread1 = CreateThread(NULL,//讓新線程使用預設的安全性
0,//讓新線程采用與調用線程一樣的棧大小
Fun1Proc,//指定線程1入口函數的位址
NULL,//傳遞給線程1的參數 可以是結構體(包含多個資料),此處不需要使用這個參數 設為NULL
0,//線程建立标記 設為0 讓線程一旦建立就立即運作
NULL);//新線程的ID 這裡不需要使用該ID 設為NULL
CloseHandle(hThread1);//關閉新線程的句柄
hThread2 = CreateThread(NULL,0,Fun2Proc,NULL,0,NULL);
CloseHandle(hThread2);//關閉新線程的句柄
//while (++index < 1000)
cout<<"main thread is running\r\n"<<endl;
//Sleep(10);//延時10ms
system("pause");
return 0;
}
//線程1的入口函數
DWORD WINAPI Fun1Proc(LPVOID pParam)
{
//while (++index < 1000)
cout<<"thread1 is running\r\n"<<endl;
return 0;
}
 
//線程1的入口函數
DWORD WINAPI Fun2Proc(LPVOID pParam)
{
//while (++index < 1000)
cout<<"thread2 is running\r\n"<<endl;
return 0;
}
           

運作結果:

線程線程

四、線程同步

4.1定義

同步就是協同步調,按預定的先後次序進行運作。如:你說完,我再說。“同”字從字面上容易了解為一起動作。其實不是,“同”字應是指協同、協助、互相配合。如程序、線程同步,可了解為程序或線程A和B一塊配合,A執行到一定程度時要依靠B的某個結果,于是停下來,示意B運作;B依言執行,再将結果給A;A再繼續操作。

所謂同步,就是在發出一個功能調用時,在沒有得到結果之前,該調用就不傳回,同時其它線程也不能調用這個方法。按照這個定義,其實絕大多數函數都是同步調用(例如sin, isdigit等)。但是一般而言,我們在說同步、異步的時候,特指那些需要其他部件協作或者需要一定時間完成的任務。例如Window API函數SendMessage。該函數發送一個消息給某個視窗,在對方處理完消息之前,這個函數不傳回。當對方處理完畢以後,該函數才把消息處理函數所傳回的LRESULT值傳回給調用者。

在多線程程式設計裡面,一些敏感資料不允許被多個線程同時通路,此時就使用同步通路技術,保證資料在任何時刻,最多有一個線程通路,以保證資料的完整性。

4.2線程同步的方式和機制

臨界區、互斥區、事件、信号量四種方式

臨界區(Critical Section)、互斥量(Mutex)、信号量(Semaphore)、事件(Event)的差別

1、臨界區:通過對多線程的串行化來通路公共資源或一段代碼,速度快,适合控制資料通路。在任意時刻隻允許一個線程對共享資源進行通路,如果有多個線程試圖通路公共資源,那麼在有一個線程進入後,其他試圖通路公共資源的線程将被挂起,并一直等到進入臨界區的線程離開,臨界區在被釋放後,其他線程才可以搶占;

2、互斥量:采用互斥對象機制。 隻有擁有互斥對象的線程才有通路公共資源的權限,因為互斥對象隻有一個,是以能保證公共資源不會同時被多個線程通路。互斥不僅能實作同一應用程式的公共資源安全共享,還能實作不同應用程式的公共資源安全共享;

3、信号量:它允許多個線程在同一時刻通路同一資源,但是需要限制在同一時刻通路此資源的最大線程數目;

4、事 件: 通過通知操作的方式來保持線程的同步,還可以友善實作對多個線程的優先級比較的操作。 

五、利用互斥對象實作線程同步

5.1互斥對象

互斥對象(mutex)屬于核心對象,它能確定線程擁有對單個資源的互斥通路權。互斥對象包含一個使用數量,一個線程ID和一個計數器。其中ID用于辨別系統中哪個線程目前擁有互斥對象,計數器用于指明該線程擁有互斥對象的次數。

為了建立互斥對象,需要調用函數:CreateMutex,該函數可以建立或打開一個命名的或匿名的互斥對象,然後程式就可以利用互斥對象完成線程間的同步。CreateMutex函數原型聲明如下:

HANDLE CreateMutex(
LPSECURITY_ATTRIBUTESlpMutexAttributes, // 指向安全屬性的指針
BOOLbInitialOwner, // 初始化互斥對象的所有者
LPCTSTRlpName // 指向互斥對象名的指針
);
           

參數 類型及說明

lpMutexAttributes SECURITY_ATTRIBUTES,指定一個SECURITY_ATTRIBUTES結構,或傳遞零值(将參數聲明為ByVal As Long,并傳遞零值),表示使用不允許繼承的預設描述符

bInitialOwner Long,如建立程序希望立即擁有互斥體,則設為TRUE。一個互斥體同時隻能由一個線程擁有

lpName String,指定互斥體對象的名字。用vbNullString建立一個未命名的互斥體對象。如已經存在擁有這個名字的一個事件,則打開現有的已命名互斥體。這個名字可能不與現有的事件、信号機、可等待計時器或檔案映射相符

傳回值

Long,如執行成功,就傳回互斥體對象的句柄;零表示出錯。會設定GetLastError。即使傳回的是一個有效句柄,但倘若指定的名字已經存在,GetLastError也會設為ERROR_ALREADY_EXISTS 

5.2示例

#include "stdafx.h"
#include<windows.h>//需要通路Windows API函數CreateThread
#include <iostream>
using namespace std;
DWORD WINAPI Fun1Proc(//注意線程函數的聲明形式
LPVOID pParam //thread data
);
DWORD WINAPI Fun2Proc(LPVOID pParam);
int index=0;
int tickets=100;
HANDLE hMutex;
int _tmain(int argc, _TCHAR* argv[])
{
HANDLE hThread1;
HANDLE hThread2;
hThread1 = CreateThread(NULL,//讓新線程使用預設的安全性
0,//讓新線程采用與調用線程一樣的棧大小
Fun1Proc,//指定線程1入口函數的位址
NULL,//傳遞給線程1的參數 可以是結構體(包含多個資料),此處不需要使用這個參數 設為NULL
0,//線程建立标記 設為0 讓線程一旦建立就立即運作
NULL);//新線程的ID 這裡不需要使用該ID 設為NULL
hThread2 = CreateThread(NULL,0,Fun2Proc,NULL,0,NULL);
CloseHandle(hThread1);//關閉新線程的句柄
CloseHandle(hThread2);//關閉新線程的句柄
//建立互斥對象
hMutex=CreateMutex(NULL,//讓互斥對象擁有預設的安全性
FALSE,//該線程将不會獲得所建立的互斥對象的所有權 true 相反
NULL);//建立一個匿名的互斥對象
Sleep(4000);//延時4s
system("pause");
return 0;
}
 
//線程1的入口函數
DWORD WINAPI Fun1Proc(LPVOID pParam)
{
while (true)
{
WaitForSingleObject(hMutex,INFINITE);
if (tickets>0)
{
Sleep(1);
cout<<"thread1 sell tickets:\r\n"<<tickets--<<endl;
} 
else
{
break;
}
ReleaseMutex(hMutex);//釋放互斥對象
}
return 0;
}
 
//線程2的入口函數
DWORD WINAPI Fun2Proc(LPVOID pParam)
{
while (true)
{
//實作線程必須主動請求共享對象的使用權才有可能獲得該所有權
WaitForSingleObject(hMutex,//所請求對象的句柄
INFINITE);//指定等待的時間間隔,以毫秒為機關
if (tickets>0)
{
Sleep(1);
cout<<"thread2 sell tickets:\r\n"<<tickets--<<endl;
} 
else
{
break;
}
ReleaseMutex(hMutex);
}
return 0;
} 
           

運作結果:

線程線程

繼續閱讀