一:利用事件实现线程同步
1.createthread函数的用法
hThread = CreateThread(&security_attributes, dwStackSize, ThreadProc,pParam, dwFlags, &idThread) ;
HANDLE CreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes,
DWORD dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
DWORD dwCreationFlags,
LPDWORD lpThreadId
);
第一个参数是指向SECURITY_ATTRIBUTES型态的结构的指针。在Windows 98中忽略该参数。在Windows NT中,它被设为NULL。
第二个参数是用于新线程的初始堆栈大小,默认值为0。在任何情况下,Windows根据需要动态延长堆栈的大小。
第三个参数是指向线程函数的指标。函数名称没有限制,但是必须以下列形式声明:
DWORD WINAPI ThreadProc (LPVOID pParam) ;
第四个参数为传递给ThreadProc的参数。这样主线程和从属线程就可以共享数据,这个参数既可以是数值,也可以是指向其他信息的指针。
第五个参数通常为0,表示创建之后立刻运行,但当建立的线程不马上执行时为可设置为CREATE_SUSPENDED。线程将暂停直到呼叫ResumeThread来恢复线程的执行为止。
第六个参数时一个返回值,它用来接收线程的ID,可以为该参数传递NULL,表明我们对该线程的ID不感兴趣。
函数返回值为新线程的句柄。
2.CreateEvent函数
HANDLECreateEvent(
LPSECURITY_ATTRIBUTESlpEventAttributes,// 安全属性
BOOLbManualReset,// 复位方式
BOOLbInitialState,// 初始状态
LPCTSTRlpName // 对象名称
);
lpEventAttributes[输入]
一个指向SECURITY_ATTRIBUTES结构的指针,确定返回的句柄是否可被子进程继承。如果lpEventAttributes是NULL,此句柄不能被继承。
Windows NT/2000:lpEventAttributes的结构中的成员为新的事件指定了一个安全符。如果lpEventAttributes是NULL,事件将获得一个默认的安全符。
bManualReset[输入]
指定将事件对象创建成手动复原还是自动复原。如果是TRUE,那么必须用ResetEvent函数来手工将事件的状态复原到无信号状态。如果设置为FALSE,当一个等待线程被释放以后,系统将会自动将事件状态复原为无信号状态。
bInitialState[输入]
指定事件对象的初始状态。如果为TRUE,初始状态为有信号状态;否则为无信号状态。
lpName[输入]
指定事件的对象的名称,是一个以0结束的字符串指针。名称的字符格式限定在MAX_PATH之内。名字是对大小写敏感的。
如果lpName指定的名字,与一个存在的命名的事件对象的名称相同,函数将请求EVENT_ALL_ACCESS来访问存在的对象。这时候,由于bManualReset和bInitialState参数已经在创建事件的进程中设置,这两个参数将被忽略。如果lpEventAttributes是参数不是NULL,它将确定此句柄是否可以被继承,但是其安全描述符成员将被忽略。
如果lpName为NULL,将创建一个无名的事件对象。
如果lpName的和一个存在的信号、互斥、等待计时器、作业或者是文件映射对象名称相同,函数将会失败,在GetLastError函数中将返回ERROR_INVALID_HANDLE。造成这种现象的原因是这些对象共享同一个命名空间。
终端服务(Terminal Services):名称中可以加入"Global\"或是"Local\"的前缀,这样可以明确的将对象创建在全局的或事务的命名空间。名称的其它部分除了反斜杠(\),可以使用任意字符。详细内容可参考Kernel Object Name Spaces。
Windows 2000:在Windows 2000系统中,没有终端服务运行,"Global\"和"Local\"前缀将被忽略。名称的其它部分除了反斜杠(\),可以使用任意字符。
Windows NT 4.0以及早期版本,Windows 95/98:名称中除了反斜杠(\),可以使用任意字符。
// MultiThread.cpp : 定义控制台应用程序的入口点。
代码一:线程不同步的例子(基于事件)
#include "stdafx.h"
#include "stdio.h"
#include "windows.h"
DWORD WINAPI Fun1Proc(LPVOID lpParameter);
DWORD WINAPI Fun2Proc(LPVOID lpParameter);
HANDLE g_hEvent;
int tiket=100;
int _tmain(int argc, _TCHAR* argv[])
{
HANDLE Thread1;
HANDLE Thread2;
g_hEvent=CreateEvent(NULL,FALSE,FALSE,NULL); Thread1=CreateThread(NULL,0,Fun1Proc,NULL,0,NULL);
Thread2=CreateThread(NULL,0,Fun2Proc,NULL,0,NULL);
CloseHandle(Thread1);
CloseHandle(Thread2);
SetEvent(g_hEvent);
//Sleep(10);
system("pause");
return 0;
}
DWORD WINAPI Fun1Proc(LPVOID lpParameter)
{
while(1)
{
//Sleep(1);
if(tiket>0)
{
WaitForSingleObject(g_hEvent,INFINITE);
printf("Thread1 sell tiket %d\n",tiket);
tiket--;
SetEvent(g_hEvent);
}
else
{
//SetEvent(g_hEvent);
break;
}
}
return 0;
}
DWORD WINAPI Fun2Proc(LPVOID lpParameter)
{
while(1)
{
//Sleep(1);
WaitForSingleObject(g_hEvent,INFINITE);
if(tiket>0)
{
printf("Thread2 sell tiket %d\n",tiket);
tiket--;
SetEvent(g_hEvent);
}
else
{
//SetEvent(g_hEvent);
break;
}
}
return 0;
}
代码二:线程同步的例子(基于事件)
#include "stdafx.h"
#include "stdio.h"
#include "windows.h"
DWORD WINAPI Fun1Proc(LPVOID lpParameter);
DWORD WINAPI Fun2Proc(LPVOID lpParameter);
HANDLE g_hEvent;
int tiket=100;
int _tmain(int argc, _TCHAR* argv[])
{
HANDLE Thread1;
HANDLE Thread2;
g_hEvent=CreateEvent(NULL,FALSE,FALSE,NULL); Thread1=CreateThread(NULL,0,Fun1Proc,NULL,0,NULL);
Thread2=CreateThread(NULL,0,Fun2Proc,NULL,0,NULL);
CloseHandle(Thread1);
CloseHandle(Thread2);
SetEvent(g_hEvent);
//Sleep(10);
system("pause");
return 0;
}
DWORD WINAPI Fun1Proc(LPVOID lpParameter)
{
while(1)
{
//Sleep(1);
WaitForSingleObject(g_hEvent,INFINITE);
if(tiket>0)
{
printf("Thread1 sell tiket %d\n",tiket);
tiket--;
SetEvent(g_hEvent);
}
else
{
//SetEvent(g_hEvent);
break;
}
}
return 0;
}
DWORD WINAPI Fun2Proc(LPVOID lpParameter)
{
while(1)
{
//Sleep(1);
WaitForSingleObject(g_hEvent,INFINITE);
if(tiket>0)
{
printf("Thread2 sell tiket %d\n",tiket);
tiket--;
SetEvent(g_hEvent);
}
else
{
//SetEvent(g_hEvent);
break;
}
}
return 0;
}
二:利用互斥对象实现线程同步
CreateMutex()函数可用来创建一个有名或无名的互斥量对象,该对象是一个内核对象,它能够确保线程对单个资源的互斥访问权。
其函数原型为:
HANDLE CreateMutex(
LPSECURITY_ATTRIBUTESlpMutexAttributes, // 指向安全属性的指针
BOOLbInitialOwner, // 初始化互斥对象的所有者
LPCTSTRlpName // 指向互斥对象名的指针
);
返回值:
如执行成功,就返回互斥体对象的句柄;零表示出错。会设置GetLastError。即使返回的是一个有效句柄,但倘若指定的名字已经存在,GetLastError也会设为ERROR_ALREADY_EXISTS。
参数表:
lpMutexAttributes SECURITY_ATTRIBUTES,指定一个SECURITY_ATTRIBUTES结构,
可以赋值NULL,表示让互斥对象拥有默认的安全属性。
bInitialOwner Long:若为TRUE,则创建该互斥对象的线程拥有该互斥对象的所有权,否则,没有所有权。
lpName String,指定互斥体对象的名字。若为NULL,表示创建一个匿名的互斥对象。
注意:如果线程对共享资源的访问结束后,应该释放互斥对象的所有权,即将互斥对象置于有信号的状态,使用函数
BOOL WIANPI ReleaseMutex(
HANDLE hMutex
);
返回值:函数返回成功,返回非0,否则返回0;
参数:互斥对象的句柄。
对于互斥对象遵循谁拥有谁释放的原则。
代码三:
#include "stdafx.h"
#include "stdio.h"
#include "windows.h"
DWORD WINAPI Fun1Proc(LPVOID lpParameter);
DWORD WINAPI Fun2Proc(LPVOID lpParameter);
HANDLE h_Mutex;
int tiket=100;
int _tmain(int argc, _TCHAR* argv[])
{
HANDLE Thread1;
HANDLE Thread2;
h_Mutex=CreateMutex(NULL,FALSE,NULL);
Thread1=CreateThread(NULL,0,Fun1Proc,NULL,0,NULL);
Thread2=CreateThread(NULL,0,Fun2Proc,NULL,0,NULL);
CloseHandle(Thread1);
CloseHandle(Thread2);
//Sleep(10);
system("pause");
return 0;
}
DWORD WINAPI Fun1Proc(LPVOID lpParameter)
{
while(1)
{
//Sleep(1);
WaitForSingleObject(h_Mutex,INFINITE);
if(tiket>0)
{
printf("Thread1 sell tiket %d\n",tiket);
tiket--;
ReleaseMutex(h_Mutex);
}
else
{
break;
}
}
return 0;
}
DWORD WINAPI Fun2Proc(LPVOID lpParameter)
{
while(1)
{
//Sleep(1);
WaitForSingleObject(h_Mutex,INFINITE);
if(tiket>0)
{
printf("Thread2 sell tiket %d\n",tiket);
tiket--;
ReleaseMutex(h_Mutex);
}
else
{
break;
}
}
return 0;
}
三:利用关键代码段(临界区)实现线程同步
关键代码段,也称为临界区,工作在用户方式下,它是指一小段代码,在代码能够执行前,它必须独占对某些资源的访问权。
VOID InitializeCriticalSection(LPCRITICAL_SECTION lpCriticalSection )
函数功能初始化一个临界资源对象。该函数无返回值。单进程的各个线程可以使用临界资源对象来解决同步互斥问题,该对象不能保证哪个线程能够获得到临界资源对象,该系统能公平的对待每一个线程。lpCriticalSection 临界资源对象指针,该参数是一个out类型。 EnterCriticalSection(__inout LPCRITICAL_SECTION lpCriticalSection);
该函数用以获得指定的临界区的对象的所有权,该函数等待临界区对象的所有权,如果该所有权赋予了调用线程,则该函数返回,否则该函数会一直等待,从而导致线程等待。
LeaveCriticalSection(__inout LPCRITICAL_SECTION lpCriticalSection);
线程使用完所保护的资源后,需要调用该函数,释放指定的临界区对象的所有权,这时候其他想要获得该临界区对象所有权的线程就可以获得该所有权,从而进入关键代码段,访问保护的资源。
DeleteCriticalSection(__inout LPCRITICAL_SECTION lpCriticalSection);
该函数释放一个没有被任何线程所拥有的临界区对象的所有资源。
代码四:
#include "stdafx.h"
#include "stdio.h"
#include "windows.h"
DWORD WINAPI Fun1Proc(LPVOID lpParameter);
DWORD WINAPI Fun2Proc(LPVOID lpParameter);
CRITICAL_SECTION g_cs;
int tiket=100;
int _tmain(int argc, _TCHAR* argv[])
{
HANDLE Thread1;
HANDLE Thread2;
Thread1=CreateThread(NULL,0,Fun1Proc,NULL,0,NULL);
Thread2=CreateThread(NULL,0,Fun2Proc,NULL,0,NULL);
CloseHandle(Thread1);
CloseHandle(Thread2);
InitializeCriticalSection(&g_cs);
//Sleep(10);
system("pause");
DeleteCriticalSection(&g_cs);
return 0;
}
DWORD WINAPI Fun1Proc(LPVOID lpParameter)
{
while(1)
{
//Sleep(1);
EnterCriticalSection(&g_cs);
if(tiket>0)
{
printf("Thread1 sell tiket %d\n",tiket);
tiket--;
LeaveCriticalSection(&g_cs);
}
else
{
break;
}
}
return 0;
}
DWORD WINAPI Fun2Proc(LPVOID lpParameter)
{
while(1)
{
//Sleep(1);
EnterCriticalSection(&g_cs);
if(tiket>0)
{
printf("Thread2 sell tiket %d\n",tiket);
tiket--;
LeaveCriticalSection(&g_cs);
}
else
{
break;
}
}
return 0;
}
四:线程死锁
解释:对于多线程来说,如果线程1拥有了临界区对象A,等待临界区对象B的拥有权,线程2拥有了临界区对象B,等待临界区对象A的所有权,这就造成了死锁。
代码五:线程死锁
#include "stdafx.h"
#include "stdio.h"
#include "windows.h"
DWORD WINAPI Fun1Proc(LPVOID lpParameter);
DWORD WINAPI Fun2Proc(LPVOID lpParameter);
CRITICAL_SECTION g_cs;
CRITICAL_SECTION g_cs1;
int tiket=100;
int _tmain(int argc, _TCHAR* argv[])
{
HANDLE Thread1;
HANDLE Thread2;
Thread1=CreateThread(NULL,0,Fun1Proc,NULL,0,NULL);
Thread2=CreateThread(NULL,0,Fun2Proc,NULL,0,NULL);
CloseHandle(Thread1);
CloseHandle(Thread2);
InitializeCriticalSection(&g_cs);
InitializeCriticalSection(&g_cs1);
//Sleep(10);
printf("I am in the main Function!\n");
Sleep(10000);
//system("pause");
DeleteCriticalSection(&g_cs);
DeleteCriticalSection(&g_cs1);
return 0;
}
DWORD WINAPI Fun1Proc(LPVOID lpParameter)
{
while(1)
{
//Sleep(1);
EnterCriticalSection(&g_cs);
Sleep(1);
EnterCriticalSection(&g_cs1);
if(tiket>0)
{
printf("Thread1 sell tiket %d\n",tiket);
tiket--;
LeaveCriticalSection(&g_cs);
LeaveCriticalSection(&g_cs1);
}
else
{
break;
}
}
return 0;
}
DWORD WINAPI Fun2Proc(LPVOID lpParameter)
{
while(1)
{
//Sleep(1);
EnterCriticalSection(&g_cs);
Sleep(1);
EnterCriticalSection(&g_cs1);
if(tiket>0)
{
printf("Thread2 sell tiket %d\n",tiket);
tiket--;
LeaveCriticalSection(&g_cs);
LeaveCriticalSection(&g_cs1);
}
else
{
break;
}
}
return 0;
}
五:总结
互斥对象、事件对象和关键代码段的比较
1.互斥对象和事件对象都属于内核对象,利用内核对象进行线程同步时,速度较慢,但利用互斥对象和事件对象时,可以在多个进程中的多个线程间进行同步。
2.关键代码段工作在用户方式下,同步速度较快,但在使用关键代码段时,很容易进入死锁状态,因为在等待进入关键代码段时无法设置超时值。