三、 程序代码
四、 小结
对复杂的应用程序来说,线程的应用给应用程序提供了高效、快速、安全的数据处理能力。本实例讲述了线程处理中经常遇到的问题,希望对读者朋友有一定的帮助,起到抛砖引玉的作用。
//////////////////////////////////////////////////////////////////////////////////////
// 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
四、 小结
对复杂的应用程序来说,线程的应用给应用程序提供了高效、快速、安全的数据处理能力。本实例讲述了线程处理中经常遇到的问题,希望对读者朋友有一定的帮助,起到抛砖引玉的作用。