三、 程序代碼
四、 小結
對復雜的應用程序來說,線程的應用給應用程序提供了高效、快速、安全的數據處理能力。本實例講述了線程處理中經常遇到的問題,希望對讀者朋友有一定的幫助,起到拋磚引玉的作用。
//////////////////////////////////////////////////////////////////////////////////////
// sequence.cpp : Defines the entry point for the console application.
/*
主要用到的WINAPI線程控制函數,有關詳細說明請查看MSDN;
線程建立函數:
HANDLE CreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes, // 安全屬性結構指針,可為NULL;
DWORD dwStackSize, // 線程棧大小,若為0表示使用默認值;
LPTHREAD_START_ROUTINE lpStartAddress, // 指向線程函數的指針;
LPVOID lpParameter, // 傳遞給線程函數的參數,可以保存一個指針值;
DWORD dwCreationFlags, // 線程建立是的初始標記,運行或掛起;
LPDWORD lpThreadId // 指向接收線程號的DWORD變量;
);
對臨界資源控制的多線程控制的信號函數:
HANDLE CreateEvent(
LPSECURITY_ATTRIBUTES lpEventAttributes, // 安全屬性結構指針,可為NULL;
BOOL bManualReset, // 手動清除信號標記,TRUE在WaitForSingleObject后必須手動//調用RetEvent清除信號。若為 FALSE則在WaitForSingleObject
//后,系統自動清除事件信號;
BOOL bInitialState, // 初始狀態,TRUE有信號,FALSE無信號;
LPCTSTR lpName // 信號量的名稱,字符數不可多於MAX_PATH;
//如果遇到同名的其他信號量函數就會失敗,如果遇
//到同類信號同名也要注意變化;
);
HANDLE CreateMutex(
LPSECURITY_ATTRIBUTES lpMutexAttributes, // 安全屬性結構指針,可為NULL
BOOL bInitialOwner, // 當前建立互斥量是否占有該互斥量TRUE表示占有,
//這樣其他線程就不能獲得此互斥量也就無法進入由
//該互斥量控制的臨界區。FALSE表示不占有該互斥量
LPCTSTR lpName // 信號量的名稱,字符數不可多於MAX_PATH如果
//遇到同名的其他信號量函數就會失敗,
//如果遇到同類信號同名也要注意變化;
);
//初始化臨界區信號,使用前必須先初始化
VOID InitializeCriticalSection(
LPCRITICAL_SECTION lpCriticalSection // 臨界區變量指針
);
//阻塞函數
//如果等待的信號量不可用,那么線程就會掛起,直到信號可用
//線程才會被喚醒,該函數會自動修改信號,如Event,線程被喚醒之后
//Event信號會變得無信號,Mutex、Semaphore等也會變。
DWORD WaitForSingleObject(
HANDLE hHandle, // 等待對象的句柄
DWORD dwMilliseconds // 等待毫秒數,INFINITE表示無限等待
);
//如果要等待多個信號可以使用WaitForMutipleObject函數
*/
#include "stdafx.h"
#include "stdlib.h"
#include "memory.h"
HANDLE evtTerminate; //事件信號,標記是否所有子線程都執行完
/*
下面使用了三種控制方法,你可以注釋其中兩種,使用其中一種。
注意修改時要連帶修改臨界區PrintResult里的相應控制語句
*/
HANDLE evtPrint; //事件信號,標記事件是否已發生
//CRITICAL_SECTION csPrint; //臨界區
//HANDLE mtxPrint; //互斥信號,如有信號表明已經有線程進入臨界區並擁有此信號
static long ThreadCompleted = 0;
/* 用來標記四個子線程中已完成線程的個數,當一個子線程完成時就對ThreadCompleted進行加一操作, 要使用InterlockedIncrement(long* lpAddend)和InterlockedDecrement(long* lpAddend)進行加減操作*/
//下面的結構是用於傳送排序的數據給各個排序子線程
struct MySafeArray
{
long* data;
int iLength;
};
//打印每一個線程的排序結果
void PrintResult(long* Array, int iLength, const char* HeadStr = "sort");
//排序函數
unsigned long __stdcall BubbleSort(void* theArray); //冒泡排序
unsigned long __stdcall SelectSort(void* theArray); //選擇排序
unsigned long __stdcall HeapSort(void* theArray); //堆排序
unsigned long __stdcall InsertSort(void* theArray); //插入排序
/*以上四個函數的聲明必須適合作為一個線程函數的必要條件才可以使用CreateThread
建立一個線程。
(1)調用方法必須是__stdcall,即函數參數壓棧順序由右到左,而且由函數本身負責
棧的恢復, C和C++默認是__cdecl, 所以要顯式聲明是__stdcall
(2)返回值必須是unsigned long
(3)參數必須是一個32位值,如一個指針值或long類型
(4) 如果函數是類成員函數,必須聲明為static函數,在CreateThread時函數指針有特殊的寫法。如下(函數是類CThreadTest的成員函數中):
static unsigned long _stdcall MyThreadFun(void* pParam);
handleRet = CreateThread(NULL, 0, &CThreadTestDlg::MyThreadFun, NULL, 0, &ThreadID);
之所以要聲明為static是由於,該函數必須要獨立於對象實例來使用,即使沒有聲明實例也可以使用。*/
int QuickSort(long* Array, int iLow, int iHigh); //快速排序
int main(int argc, char* argv[])
{
long data[] = {123,34,546,754,34,74,3,56};
int iDataLen = 8;
//為了對各個子線程分別對原始數據進行排序和保存排序結果
//分別分配內存對data數組的數據進行復制
long *data1, *data2, *data3, *data4, *data5;
MySafeArray StructData1, StructData2, StructData3, StructData4;
data1 = new long[iDataLen];
memcpy(data1, data, iDataLen << 2); //把data中的數據復制到data1中
//內存復制 memcpy(目標內存指針, 源內存指針, 復制字節數), 因為long的長度
//為4字節,所以復制的字節數為iDataLen << 2, 即等於iDataLen*4
StructData1.data = data1;
StructData1.iLength = iDataLen;
data2 = new long[iDataLen];
memcpy(data2, data, iDataLen << 2);
StructData2.data = data2;
StructData2.iLength = iDataLen;
data3 = new long[iDataLen];
memcpy(data3, data, iDataLen << 2);
StructData3.data = data3;
StructData3.iLength = iDataLen;
data4 = new long[iDataLen];
memcpy(data4, data, iDataLen << 2);
StructData4.data = data4;
StructData4.iLength = iDataLen;
data5 = new long[iDataLen];
memcpy(data5, data, iDataLen << 2);
unsigned long TID1, TID2, TID3, TID4;
//對信號量進行初始化
evtTerminate = CreateEvent(NULL, FALSE, FALSE, "Terminate");
evtPrint = CreateEvent(NULL, FALSE, TRUE, "PrintResult");
//分別建立各個子線程
CreateThread(NULL, 0, &BubbleSort, &StructData1, NULL, &TID1);
CreateThread(NULL, 0, &SelectSort, &StructData2, NULL, &TID2);
CreateThread(NULL, 0, &HeapSort, &StructData3, NULL, &TID3);
CreateThread(NULL, 0, &InsertSort, &StructData4, NULL, &TID4);
//在主線程中執行行快速排序,其他排序在子線程中執行
QuickSort(data5, 0, iDataLen - 1);
PrintResult(data5, iDataLen, "Quick Sort");
WaitForSingleObject(evtTerminate, INFINITE); //等待所有的子線程結束
//所有的子線程結束后,主線程才可以結束
delete[] data1;
delete[] data2;
delete[] data3;
delete[] data4;
CloseHandle(evtPrint);
return 0;
}
/*
冒泡排序思想(升序,降序同理,后面的算法一樣都是升序):從頭到尾對數據進行兩兩比較進行交換,小的放前大的放后。這樣一次下來,最大的元素就會被交換的最后,然后下一次
循環就不用對最后一個元素進行比較交換了,所以呢每一次比較交換的次數都比上一次循環的次數少一,這樣N次之后數據就變得升序排列了*/
unsigned long __stdcall BubbleSort(void* theArray)
{
long* Array = ((MySafeArray*)theArray)->data;
int iLength = ((MySafeArray*)theArray)->iLength;
int i, j=0;
long swap;
for (i = iLength-1; i >0; i--)
{
for(j = 0; j < i; j++)
{
if(Array[j] >Array[j+1]) //前比后大,交換
{
swap = Array[j];
Array[j] = Array[j+1];
Array[j+1] = swap;
}
}
}
PrintResult(Array, iLength, "Bubble Sort"); //向控制台打印排序結果
InterlockedIncrement(&ThreadCompleted); //返回前使線程完成數標記加1
if(ThreadCompleted == 4) SetEvent(evtTerminate); //檢查是否其他線程都已執行完
//若都執行完則設置程序結束信號量
return 0;
}
/* 選擇排序思想:每一次都從無序的數據中找出最小的元素,然后和前面已經有序的元素序列的后一個元素進行交換,這樣整個源序列就會分成兩部分,前面一部分是 已經排好序的有序序列,后面一部分是無序的,用於選出最小的元素。循環N次之后,前面的有序序列加長到跟源序列一樣長,后面的無序部分長度變為0,排序就 完成了。*/
unsigned long __stdcall SelectSort(void* theArray)
{
long* Array = ((MySafeArray*)theArray)->data;
int iLength = ((MySafeArray*)theArray)->iLength;
long lMin, lSwap;
int i, j, iMinPos;
for(i=0; i < iLength-1; i++)
{
lMin = Array[i];
iMinPos = i;
for(j=i + 1; j <= iLength-1; j++) //從無序的元素中找出最小的元素
{
if(Array[j] < lMin)
{
iMinPos = j;
lMin = Array[j];
}
}
//把選出的元素交換拼接到有序序列的最后
lSwap = Array[i];
Array[i] = Array[iMinPos];
Array[iMinPos] = lSwap;
}
PrintResult(Array, iLength, "Select Sort"); //向控制台打印排序結果
InterlockedIncrement(&ThreadCompleted); //返回前使線程完成數標記加1
if(ThreadCompleted == 4) SetEvent(evtTerminate);//檢查是否其他線程都已執行完
//若都執行完則設置程序結束信號量
return 0;
}
/* 堆排序思想:堆:數據元素從1到N排列成一棵二叉樹,而且這棵樹的每一個子樹的根都是該樹中的元素的最小或最大的元素這樣如果一個無序數據集合是一個堆那 么,根元素就是最小或最大的元素堆排序就是不斷對剩下的數據建堆,把最小或最大的元素析透出來。下面的算法,就是從最后一個元素開始,依據一個節點比父節 點數值大的原則對所有元素進行調整,這樣調整一次就形成一個堆,第一個元素就是最小的元素。然后再對剩下的無序數據再進行建堆,注意這時后面的無序數據元 素的序數都要改變,如第一次建堆后,第二個元素就會變成堆的第一個元素。*/
unsigned long __stdcall HeapSort(void* theArray)
{
long* Array = ((MySafeArray*)theArray)->data;
int iLength = ((MySafeArray*)theArray)->iLength;
int i, j, p;
long swap;
for(i=0; i
四、 小結
對復雜的應用程序來說,線程的應用給應用程序提供了高效、快速、安全的數據處理能力。本實例講述了線程處理中經常遇到的問題,希望對讀者朋友有一定的幫助,起到拋磚引玉的作用。
