Windows编程 同步对象和技术

在多线程编程中,为了保证线程正确的运行,必须进行同步。Windows操作系统支持多种同步对象:
同步对象:互斥量(Mutex)
互斥量的一个重要特性是,只允许一个线程拥有它。
创建一个互斥量对象,可以使用CreateMutex函数

HANDLE WINAPI CreateMutex(
  LPSECURITY_ATTRIBUTES  lpMutexAttributes,
  BOOL  bInitialOwner,
  LPCSTR  szName
)
用OpenMutex打开互斥量
HANDLE WINAPI OpenMutex(
  DWORD  dwDesiredAccess,
  BOOL  bInheritHandle,
  LPCTSTR  szName
)
线程操作完毕之后,必须释放互斥量一遍让其他线程获取。可以使用ReleaseMutex API 释放互斥量。
BOOL WINAPI ReleaseMutex(
  HANDLE  hMutex
)
最后用CloseHandle API关闭句柄
BOOL CloseHandle(
  HANDLE  hMutex
)

同步对象:信号量(semaphore)
信号量与互斥量类似,唯一的区别是多个对象可持有信号量的所有权。
创建信号量可以用CreateSemaphore API。
HANDLE WINAPI CreateSemaphore(
  LPSECURITY_ATTRIBUTES  lpSemaphoreAttributes,
  LONG  lInitialCount,
  LONG  lMaximumCount,
  LPCTSTR  szName
)
打开已有的信号量可以使用OpenSemaphore API
HANDLE WINAPI OpenSemaphore(
  DWORD  dwDesiredAccess,
  BOOL  bInherithandle,
  LPCTSTR  szName
)
线程操作完毕之后,必须释放信号量以减少计数
BOOL WINAPI ReleaseSemaphore(
  HANDLE  hSemahore,
  LONG  lReleaseCount,
  LPLONG  lpPreviousCount
)
最后用CloseHandle API关闭句柄
BOOL CloseHandle(
  HANDLE  hMutex
)

同步对象:事件(Event)
事件对象播报任何线程都能收听到的公共信号。
创建时间对象
HANDLE WINAPI CreateEvent(
  LPSECURITY_ATTRIBUTE  lpEventAttributes,
  BOOL  bManualReset,
  BOOL  bInitialState,
  LPCESTR  szName
)
HANDLE WINAPI CreateEventEx(
  LPSECURITY_ATTRIBUTES  lpEventAttributes,
  LPCTSTR  szName,
  DWORD  dwFlags,
  DWORD  dwDesiredAccess
)
打开一个现有的事件对象
HANDLE WINAPI OpenEvent(
  DWORD  dwDesiredAccess,
  BOOL  bInheritHandle,
  LPCTSTR  szName
)
设置事件对象
BOOL WINAPI SetEvent(
  HANDLE  hEvent;
)
重置事件对象
BOOL WINAPI ResetEvent(
  HANDLE  hEvent;
)
可以脉冲一个对象
BOOL WINAPI PulseEvent(
  HANDLE  hEvent;
)
最后用CloseHandle API关闭句柄
BOOL CloseHandle(
  HANDLE  hMutex
)

同步对象:临界区(Critical_Section)
临界区执行的功能与互斥量相同,不同的是临界区不能共享,它只对一个进程可见。
要先声明临界区才能使用它
CRITICAL_SECTION cs;
接着要初始化它:
void WINAPI InitializeCriticaSection(
  LPCRITICAL_SECTION  lpCriticalSection;
)

然后,通过调用EnterCriticalSection 或者TryEnterCriticalSection API让一个线程进入临界区:
void WINAPI EnterCriticalSection(
  LPCRITICAL_SECTION  lpCriticalSection;
)
BOOL WINAPI TryEnterCriticalSection(
  LPCRITICAL_SECTION  lpCriticalSection;
)
线程完成任务后,必须调用LeaveCriticalSection API离开临界区:
void WINAPI LeaveCriticalSection(
  LPCRITICAL_SECTION  lpCriticalSection;
)
接着调用DeleteCriticalSection API释放资源:
void WINAPI DeleteCriticalSection(
  LPCRITICAL_SECTION  lpCriticalSection;
)
同步对象:管道(Pipe)
管道是一种用于在进程间共享数据的机制,其实质是一段共享内存。Windows系统为这段共享的内存设计采用数据流I/0的方式来访问。由一个进程读、另一个进程写,类似于一个管道两端,因此这种进程间的通信方式称作“管道”。
管道分为匿名管道和命名管道。

1.匿名管道只能在父子进程间进行通信,不能在网络间通信,而且数据传输是单向的,只能一端写,另一端读。

2.命令管道可以在任意进程间通信,通信是双向的,任意一端都可读可写,但是在同一时间只能有一端读、一端写。

匿名管道:
创建管道:
BOOL WINAPI CreatePipe(
  PHANDLE  hReadPipe,//读取端句柄
  PHANDLE  hWritePipe,//输入端句柄
   LPSECURITY_ATTRIBUTES  lpPipeAttributes,//安全属性
  DWORD  nSize// 管道的缓冲区容量,NULL表示默认大小
);
读取管道内数据:
BOOL ReadFile(
   HANDLE  hFile,//句柄,可以是标准输入输出流或文件或管道
   LPVOID  lpBuffer, //读取的数据写入缓冲区
   DWORD  nNumberOfBytesToRead,//指定读取的字节数
   LPDWORD  lpNumberOfBytesRead,//实际读取的字节数
   LPOVERLAPPED  lpOverlapped//用于异步操作,一般置为NULL
);
向管道写入数据:
BOOL WriteFile(
   HANDLE  hFile,//句柄,同上
   LPCVOID  lpBuffer,//指定待写入的数据
   DWORD  nNumberOfBytesToWrite,//写入的数据量
   LPDWORD  lpNumberOfBytesWritten,//实际要写的数据量
  LPOVERLAPPED  lpOverlapped//一般置为NULL
);

为实现父子进程间的通信,需要对子进程的管道进行重定向:
我们知道创建子进程函数 CreateProcess中有一个参数STARUIINFO,默认情况下子进程的输入输出管道是标准输入输出流,可以通过下面的方法实现管道重定向:
STARTUPINFO si;
si.hStdInput = hPipeInputRead; //输入由标准输入 -> 从管道中读取
si.hStdOutput = hPipeOutputWrite; //输出由标准输出 -> 输出到管道

命名管道:
服务端代码流程:
1.创建命名管道:
HANDLE WINAPI CreateNamedPipe(
  LPCTSTR  lpName,//管道名
   DWORD  dwOpenMode,//管道打开方式
   DWORD  nMaxInstances,//表示该管道所能够创建的最大实例数量。
   DWORD  nOutBufferSize,//表示管道的输出缓冲区容量,为0表示使用默认大小。
   DWORD  nInBufferSize,//表示管道的输入缓冲区容量,为0表示使用默认大小。
   DWORD  nDefaultTimeOut,//表示管道的默认等待超时。
  LPSECURITY_ATTRIBUTES  lpSecurityAttributes//表示管道的安全属性。
);
2.创建完成后等待连接:
BOOL WINAPI ConnectNamedPipe(
   HANDLE  hNamedPipe,//命名管道句柄
   LPOVERLAPPED  lpOverlapped//一般为NULL
);
3.读取客户端请求数据:ReadFile
4.向客户端回复数据:WriteFile
5.关闭链接:
BOOL WINAPI DisconnectNamedPipe(
  HANDLE  hNamedPipe
);
6.关闭管道:CloseHandle

客户端代码流程:
1 打开命名管道:
HANDLE WINAPI CreateFile(
  LPCTSTR   lpFileName,
  DWORD   dwDesiredAccess,
  DWORD   dwShareMode,
  LPSECURITY_ATTRIBUTES  lpSecurityAttributes,
  DWORD   dwCreationDisposition,
  DWORD   dwFlagsAndAttributes,
  HANDLE   hTemplateFile
);
2 等待服务端相应:
BOOL WINAPI WaitNamedPipe(
  LPCTSTR  lpNamedPipeName,//命名管道名称
  DWORD  nTimeOut//等待时长
);
3 切换管道为读模式:
BOOL WINAPI SetNamedPipeHandleState(
  HANDLE  hNamedPipe,
  LPDWORD  lpMode,
  LPDWORD  lpMaxCollectionCount,
  LPDWORD  lpCollectDataTimeout
);
4 向服务端发送数据:WriteFile
5 读取服务端数据:ReadFile
6 关闭管道:CloseHandle

下面讲解一下常用的WaitForSingleObject 和WaitForMultipleObjects函数。
DWORD WaitForSingleObject函数:
  DWORD  WaitForSingleObject(
  HANDLE  hObject,
  DWORD  dwMilliseconds
);
第一个参数hObject标识一个能够支持被通知/未通知的内核对象(前面列出的任何一种对象都适用)。
第二个参数dwMilliseconds允许该线程指明,为了等待该对象变为已通知状态,它将等待多长时间。(INFINITE为无限时间量,INFINITE已经定义为0xFFFFFFFF(或-1))
可以通过下面的代码理解:

DWORD dw = WaitForSingleObject(hProcess, 5000); //等待一个进程结束
switch (dw)
{
case WAIT_OBJECT_0:
// hProcess所代表的进程在5秒内结束
break;
case WAIT_TIMEOUT:
// 等待时间超过5秒
break;
case WAIT_FAILED:
// 函数调用失败,比如传递了一个无效的句柄
break;
}

还可以使用WaitForMulitpleObjects函数来等待多个内核对象变为已通知状态:
DWORD WaitForMultipleObjects(
  DWORD  dwCount, //等待的内核对象个数
  CONST HANDLE*  phObjects, //一个存放被等待的内核对象句柄的数组
  BOOL  bWaitAll, //是否等到所有内核对象为已通知状态后才返回
  DWORD  dwMilliseconds//等待时间
);

该函数的第一个参数指明等待的内核对象的个数,可以是0到MAXIMUM_WAIT_OBJECTS(64)中的一个值。phObjects参数是一个存放等待的内核对象句柄的数组。bWaitAll参数如果为TRUE,则只有当等待的所有内核对象为已通知状态时函数才返回,如果为FALSE,则只要一个内核对象为已通知状态,则该函数返回。第四个参数和WaitForSingleObject中的dwMilliseconds参数类似。
  该函数失败,返回WAIT_FAILED;如果超时,返回WAIT_TIMEOUT;如果bWaitAll参数为TRUE,函数成功则返回WAIT_OBJECT_0,如果bWaitAll为FALSE,函数成功则返回值指明是哪个内核对象收到通知。
  可以如下使用该函数:

HANDLE h[3]; //句柄数组
//三个进程句柄
h[0] = hProcess1;
h[1] = hProcess2;
h[2] = hProcess3;
DWORD dw = WaitForMultipleObjects(3, h, FALSE, 5000); //等待3个进程结束
switch (dw)
{
case WAIT_FAILED:
// 函数呼叫失败
break;
case WAIT_TIMEOUT:
// 超时
break;
case WAIT_OBJECT_0 + 0:
// h[0](hProcess1)所代表的进程结束
break;
case WAIT_OBJECT_0 + 1:
// h[1](hProcess2)所代表的进程结束
break;
case WAIT_OBJECT_0 + 2:
// h[2](hProcess3)所代表的进程结束
break;
}

更多详细信息,可以查看MSDN官网:
同步: https://msdn.microsoft.com/en-us/library/ms686360(v=vs.85).aspx
IPC:https://msdn.microsoft.com/en-us/library/aa365574(v=vs.85).aspx

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zhengjihao

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值