Windows多线程程序设计- -
1、产生一个线程,只是个框架,没有具体实现。理解::CreateThread函数用法。
#include
DWORD WINAPI ThreadFunc(LPVOID);
int main()
{
HANDLE hThread;
DWORD dwThreadID;
hThread = ::CreateThread(NULL,
0,
(LPTHREAD_START_ROUTINE)(ThreadFunc),
NULL,
0,
&dwThreadID);
...;
return 0;
}
DWORD WINAPI ThreadFunc(LPVOID lParam)
{
...;
return 0;
}
2、一个真正运转的多线程程序,当你运行它的时候,你会发现(也可能会害怕),自己试试吧。说明了多线程程序是无法预测其行为的,每次运行都会有不同的结果。
#include
#include
using namespace std;
DWORD WINAPI ThreadFunc(LPVOID);
int main()
{
HANDLE hThread;
DWORD dwThreadID;
// 产生5个线程
for(int i=0; i<5; i++)
{
hThread = ::CreateThread(NULL,
0,
(LPTHREAD_START_ROUTINE)(ThreadFunc),
(LPVOID)&i,
0,
&dwThreadID);
if(dwThreadID)
cout << "Thread launched: " << i << endl;
}
// 必须等待线程结束,以后我们用更好的处理方法
Sleep(5000);
return 0;
}
DWORD WINAPI ThreadFunc(LPVOID lParam)
{
int n = (int)lParam;
for(int i=0; i<3; i++)
{
cout << n <<","<< n <<","<< n << ","< }
return 0;
}
3、使用CloseHandle函数来结束线程,应该是“来结束核心对象的”,详细要参见windows多线程程序设计一书。
修改上面的程序,我们只简单的修改if语句。
if(dwThreadID)
{
cout << "Thread launched: " << i << endl;
CloseHandle(dwThreadID);
}
4、GetExitCodeThread函数的用法和用途,它传回的是线程函数的返回值,所以不能用GetExitCodeThread的返回值来判断线程是否结束。
#include
#include
using namespace std;
DWORD WINAPI ThreadFunc(LPVOID);
int main()
{
HANDLE hThread1;
HANDLE hThread2;
DWORD dwThreadID1;
DWORD dwThreadID2;
DWORD dwExitCode1 = 0;
DWORD dwExitCode2 = 0;
hThread1 = ::CreateThread(NULL,
0,
(LPTHREAD_START_ROUTINE)(ThreadFunc),
(LPVOID)1,
0,
&dwThreadID1);
if(dwThreadID1)
cout << "Thread launched: " << dwThreadID1 << endl;
hThread2 = ::CreateThread(NULL,
0,
(LPTHREAD_START_ROUTINE)(ThreadFunc),
(LPVOID)2,
0,
&dwThreadID2);
if(dwThreadID2)
cout << "Thread launched: " << dwThreadID2 << endl;
while(1)
{
cout<<"Press any key.";
cin.get();
GetExitCodeThread(hThread1, &dwExitCode1);
GetExitCodeThread(hThread2, &dwExitCode2);
if( dwExitCode1 == STILL_ACTIVE )
cout< if( dwExitCode2 == STILL_ACTIVE )
cout< if( dwExitCode1 != STILL_ACTIVE && dwExitCode2 != STILL_ACTIVE )
break;
}
CloseHandle(hThread1);
CloseHandle(hThread2);
cout<<"thread 1 returned: "< cout<<"thread 2 returned: "< return 0;
}
DWORD WINAPI ThreadFunc(LPVOID lParam)
{
DWORD n = (DWORD)lParam;
Sleep(n*2000);
return n*10;
}
所以,最终判断线程是否结束还是运行,运用下面的方法,这段代码很重要哦,但它始终是个busy loop,还不是最好的方法。
while(1)
{
BOOL rc;
rc = GetExitCodeThread(hThread, dwThreadID);
if(rc && dwThreadId != STILL_ACTIVE)
break;
}
5、上面我们已经提到了等待一个线程结束的问题,这里我们讲继续讲述最好的方法。使用WaitForSingleObject(HANDLE, DWORD);
同时上面的busy loop我们可以用这个函数代理了,
WaitForSingleObject(hThread, INFINISH); 看代码吧。
#include
#include
using namespace std;
const int NUM_TASKS = 6;
const int THREAD_POOL_SIZE = 3;
const int MAX_THREAD_INDEX = 2;
DWORD WINAPI ThreadFunc(LPVOID);
int main()
{
HANDLE hThread[THREAD_POOL_SIZE];
int slot = 0;
DWORD dwThreadID;
DWORD dwExitCode = 0;
for(int i=1; i {
if(i>THREAD_POOL_SIZE)
{
WaitForSingleObject(hThread[slot], INFINITE);
GetExitCodeThread(hThread[slot], &dwExitCode);
cout<<"Slot "< CloseHandle(hThread[slot]);
}
hThread[slot] = CreateThread(NULL,
0,
(LPTHREAD_START_ROUTINE)ThreadFunc,
(LPVOID)slot,
0,
&dwThreadID);
cout<<"launched thread "< if(++slot>MAX_THREAD_INDEX)
slot=0;
}
for(slot=0; slot {
WaitForSingleObject(hThread[slot], INFINITE);
CloseHandle(hThread[slot]);
}
cout<<"all thread terminated."< return 0;
}
DWORD WINAPI ThreadFunc(LPVOID lParam)
{
srand(GetTickCount());
Sleep((rand()%8)*500+500);
cout<<"slot "<<(DWORD)lParam<<" idle."< return (DWORD)lParam;
}
我们发现,调用WaitForSingleObject()并放置一个“线程核心对象”作为参数,将是调用线程#1开始睡眠,直到线程#2(我们刚刚说的线程核心对象)结束为止。就想Sleep()函数一样。INFINITE代表无穷等待,呵呵。
6、使用WaitForMultipleObject(DWORD nCount, CONST HANDLE* lpHandles, BOOL bWaitAll, DWORD dwMilliSeconds)
解释一下参数:
nCount指的是lpHandles数组元素的个数。
lpHandles指的是核心对象数组。
bWaitAll一般为TRUE。
dwMilliSeconds一般为INFINITE。
7、下面我们要接触到的是同步问题了,如果你不知道同步是什么,最好上网搜索一下。
简单的一个含有Critical_Section的链表的代码:
typedef struct _Node
{
struct _Node* next;
int data;
}Node;
typedef struct _List
{
Node* head;
Node* tail;
CRITICAL_SECTION critical_sec;
}List;
List* CreateList()
{
List *pList = new List;
pList->head = NULL;
pList->tail = NULL;
InitializeCriticalSection(&pList->critical_sec);
return pList;
}
DeleteCriticalSection(&pList->critical_sec);
do
{
Node* node;
node = pList->head;
delete node;
}while(pList->head = pList->head->next != NULL)
void AddHead(List* pList, Node* newNode)
{
EnterCriticalSection(&pList->critical_sec);
newNode->next = pList->head;
pList->head = newNode;
LeaveCriticalSection(&pList->critical_sec);
}
void AddTail(List* pList, Node* newNode)
{
EnterCriticalSection(&pList->critical_sec);
pList->tail->next = newNode;
newNode->next = NULL;
pList->tail = newNode;
LeaveCriticalSection(&pList->critical_sec);
}
Node* Next(List* pList, Node* node)
{
Node* Next;
EnterCriticalSection(&pList->critical_sec);
Next = node->next;
LeaveCriticalSection(&pList->critical_sec);
return next;
}
不知道有没有问题,自己没有测试,如果你有心就测试一下吧。
另外还有Mutex:
Mutex
CreateMutex()
OpemMutex()
WaitForSingleObject()
WaitForMultipleObjects()
MsgWaitForMultipleObjects()
ReleaseMutex()
CloseHandle()
下面是一个交换链表的Mutex操作。
struct Node
{
struct Node* next;
int data;
};
struct List
{
struct Node* head;
HANDLE hMutex;
};
void SwapLists(List* list1, List* list2)
{
List* tmp_list;
HANDLE arrHandle[2];
arrHandle[0] = list1->hMutex;
arrHandle[1] = list2->hMutex;
WaitForMultipleObjects(2, arrHandle, TRUE, INFINITE);
tmp_list = list1->head;
list1->head = list2->head;
list2->head = tmp_list;
ReleaseMutex(arrHandle[0]);
ReleaseMutex(arrHandle[1]);
}
与Critical_Section不同,Mutex可以跨进程使用,以及跨线程使用。Mutex可以根据名称被开启。所以,另一个进程可以完全不需要和产生Mutex的进程打招呼,就根据名称开启一个Mutex。这里要注意CreateMutex第二个参数。
另外还有Semphore,Event,InterlockIncrement,InterlockDecrement,其中Event有很大的灵活性,这里不举例了,感兴趣就上网搜索一下吧。
下面我们对比一下上面的几个同步对象。
Critical Section:
用来实现“排他占有”。适用范围是单一进程的各个线程之间。它是:
1、一个局部性对象,不是一个核心对象。
2、快速而有效率。
3、不能够同时有一个以上的Critical_Section被等待。
4、无法侦测是否已被某个线程放弃。
Mutex:
一个核心对象,可以在不同的线程之间实现“排他性占有”,甚至即使那些线程分属不同进程。它是:
1、一个核心对象。
2、如果拥有mutex的那个线程结束,则会产生一个“abandoned”错误信息。
3、可是使用Wait...()等待一个mutext。
4、可以具名,因此可以被其他进程开启。
5、只能被拥有它的那个线程释放(released)。
Semaphore :
被用来追踪有限资源。它是:
1、一个核心对象。
2、没有拥有者。
3、可以具名,因此可以被其他进程开启。
4、可以被任何一个线程释放(released)。
Event:
通常用于overlapped I/O,或用来设计某些自定义的同步对象。它是:
1、一个核心对象。
2、完全在程序掌控之下。
3、适用于设计新的同步对象。
4、“要求苏醒”的请求并不会被存储起来,肯能会被遗失掉。
5、可以具名,因此可以被其他进程开启。