对于线程同步与异步的学习,主要知识点摘记如下。
1.事件对象和互斥对象,一样都属于内核对象,它包含一个使用计数,一个用于标识该事件是一个自动重置还是一个人工重置的布尔值,和另一个用于指定该事件处于已通知状态还是未通知状态的布尔值。
HANDLECreateEvent(
LPSECURITY_ATTRIBUTESlpEventAttributes,// 安全属性
BOOLbManualReset,// 复位方式
BOOLbInitialState,// 初始状态
LPCTSTRlpName // 对象名称
);
lpEventAttributes[输入]
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。造成这种现象的原因是这些对象共享同一个命名空间。
如果
函数调用成功,函数返回事件对象的句柄。如果对于命名的对象,在函数调用前已经被创建,函数将返回存在的事件对象的句柄,而且在GetLastError函数中返回ERROR_ALREADY_EXISTS。
如果函数失败,函数返回值为NULL,如果需要获得详细的错误信息,需要调用GetLastError。
一个Event被创建以后,可以用
OpenEvent()API来获得它的Handle,用CloseHandle()来关闭它,用SetEvent()或PulseEvent()来设置它使其有信号,用ResetEvent()来使其无信号,用WaitForSingleObject()或WaitForMultipleObjects()来等待其变为有信号.
PulseEvent()是一个比较有意思的使用方法,正如这个API的名字,它使一个Event对象的状态发生一次脉冲变化,从无信号变成有信号再变成无信号,而整个操作是原子的.对自动复位的Event对象,它仅释放第一个等到该事件的thread(如果有),而对于人工复位的Event对象,它释放所有等待的thread.
3.
SECURITY_ATTRIBUTES结构包含一个对象的
安全描述符,并指定检索到指定这个结构的句柄是否是可继承的。这个结构为很多函数创建对象是提供安全性设置。
typedef struct _SECURITY_ATTRIBUTES {
DWORD nLength; / /结构体的大小,可用SIZEOF取得
LPVOID lpSecurityDescriptor; / /
安全描述符
BOOL bInheritHandle ;/ /安全描述的对象能否被新创建ÆÆ的进程继承
} SECURITY_ATTRIBUTES,* PSECURITY_ATTRIBUTES;
4.SetEvent()设置事件的状态为有标记,释放任意等待线程。如果事件是手工的,此事件将保持有标记直到调用ResetEvent,这种情况下将释放多个线程;如果事件是自动的,此事件将保持有标记,直到一个线程被释放,系统将设置事件的状态为无标记;如果没有线程在等待,则此事件将保持有标记,直到一个线程被释放。
CEvent::SetEvent
BOOL SetEvent(HANDLE hEvent);
其中hEvent表示句柄,返回值:如果操作成功,则返回非零值,否则为0。
5.ResetEvent这个函数把指定的
事件对象设置为无信号状态。
BOOL ResetEvent(HANDLE hEvent);
参数说明:
hEvent [in] 指向事件对象的句柄.由 CreateEvent or
OpenEvent 函数返回。 这个句柄需要拥有EVENT_MODIFY_STATE 访问权限.
函数成功,返回非0值,否则返回0值,可以调用GetLastError得到错误的详细信息。
6.关键代码段是指一个小代码段,在代码能够执行前,它必须独占对某些共享资源的访问权。这是让若干行代码能够“以原子操作方式”来使用资源的一种方法。所谓原子操作方式,是指该代码知道没有别的线程要访问该资源。当然,系统仍然能够抑制你的线程的运行,而抢先安排其他线程的运行。不过,在线程退出关键代码段之前,系统将不给想要访问相同资源的其他任何线程进行调度。
7.VOID InitializeCriticalSection(LPCRITICAL_SECTION lpCriticalSection ):函数功能初始化一个临界资源对象。“
临界区”
CCriticalSection 是临界资源对象指针。该函数无返回值。单进程的各个线程可以使用临界资源对象来解决同步互斥问题,该对象不能保证哪个线程能够获得到临界资源对象,该系统能公平的对待每一个
线程。
8.RTL_CRITICAL_SECTION 结构
struct RTL_CRITICAL_SECTION
{
PRTL_CRITICAL_SECTION_DEBUG DebugInfo;
LONG LockCount;
LONG RecursionCount;
HANDLE OwningThread;
HANDLE LockSemaphore;
ULONG_PTR SpinCount;
};
DebugInfo 此字段包含一个指针,指向系统分配的伴随结构,该结构的类型为
RTL_CRITICAL_SECTION_DEBUG。这一结构中包含更多极有价值的信息,也定义于 WINNT.H 中。我们稍后将对其进行更深入地研究。
LockCount 这是临界区中最重要的一个字段。它被初始化为数值 -1;此数值等于或大于 0
时,表示此临界区被占用。当其不等于 -1 时,OwningThread 字段(此字段被错误地定义于 WINNT.H 中 — 应当是 DWORD 而不是
HANDLE)包含了拥有此临界区的线程 ID。此字段与 (RecursionCount -1) 数值之间的差值表示有多少个其他线程在等待获得该临界区。
RecursionCount
此字段包含所有者线程已经获得该临界区的次数。如果该数值为零,下一个尝试获取该临界区的线程将会成功。
OwningThread 此字段包含当前占用此临界区的线程的线程标识符。此线程 ID 与
GetCurrentThreadId 之类的 API 所返回的 ID 相同。
LockSemaphore
此字段的命名不恰当,它实际上是一个自复位事件,而不是一个信号。它是一个内核对象句柄,用于通知操作系统:该临界区现在空闲。操作系统在一个线程第一次尝试获得该临界区,但被另一个已经拥有该临界区的线程所阻止时,自动创建这样一个句柄。应当调用
DeleteCriticalSection(它将发出一个调用该事件的 CloseHandle 调用,并在必要时释放该调试结构),否则将会发生资源泄漏。
SpinCount 仅用于多处理器系统。MSDN
文档对此字段进行如下说明:“在多处理器系统中,如果该临界区不可用,调用线程将在对与该临界区相关的信号执行等待操作之前,旋转 dwSpinCount
次。如果该临界区在旋转操作期间变为可用,该调用线程就避免了等待操作。”旋转计数可以在多处理器计算机上提供更佳性能,其原因在于在一个循环中旋转通常要快于进入内核模式等待状态。此字段默认值为零,但可以用
9.多个线程操作相同的数据时,一般是需要按顺序访问的,否则会引导数据错乱,无法控制数据,变成随机变量。为解决这个问题,就需要引入互斥变量,让每个线程都按顺序地访问变量。这样就需要使用EnterCriticalSection和LeaveCriticalSection函数。
InitializeCriticalSection(&cs);
EnterCriticalSection(&cs);
MyMoney*=10;
LeaveCriticalSection(&cs);
DeleteCriticalSection(&cs);
10.所谓死锁: 是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。
虽然进程在运行过程中,可能发生死锁,但死锁的发生也必须具备一定的条件,死锁的发生必须具备以下四个
必要条件。
1)互斥条件:指进程对所分配到的资源进行排它性使用,即在一段时间内某资源只由一个进程占用。如果此时还有其它进程请求资源,则请求者只能等待,直至占有资源的进程用毕释放。
2)请求和保持条件:指进程已经保持至少一个资源,但又提出了新的资源请求,而该资源已被其它进程占有,此时请求进程阻塞,但又对自己已获得的其它资源保持不放。
3)不剥夺条件:指进程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放。
4)环路等待条件:指在发生死锁时,必然存在一个进程——资源的环形链,即进程集合{P0,P1,P2,···,Pn}中的P0正在等待一个P1占用的资源;P1正在等待P2占用的资源,……,Pn正在等待已被P0占用的资源。
11.阻塞和非阻塞模式,Windows套接字在阻塞和非阻塞两种模式下执行I/O操作。在阻塞模式下,在I/O操作完成前,执行的操作函数一直等候而不会立即返回,该函数所在的线程会阻塞在这里。相反,在非阻塞模式下,套接字函数会立即返回,而不管I/O是否完成,该函数所在的线程会继续运行。
12.WSAAsyncSelect()函数用来请求Windows Sockets DLL为窗口句柄发一条消息-无论它何时检测到由lEvent参数指明的网络事件.要发送的消息由wMsg参数标明.被通知的套接口由s标识.
#include <winsock.h>
intPASCAL FAR WSAAsyncSelect (SOCKET s,HWND hWnd,unsigned int wMsg,long lEvent);
hWnd 标识一个在网络事件发生时需要接收消息的
窗口句柄.
wMsg 在网络事件发生时要接收的消息.
lEvent位屏蔽码,用于指明应用程序感兴趣的网络事件集合.
13.WSAEnumProtocols()获取现有传送协议的相关信息。
#include <winsock2.h>
int WSAAPI WSAEnumProtocols ( LPINT lpdwProtocols,LPWSAPROTOCOL_INFO lpProtocolBuffer,LPDWORD lpdwBufferLength);
lpdwProtocols:一个以NULL结尾的协议标识号
数组。本参数可选;如果lpdwProtocols为 NULL,则返回所有可用协议的信息,否则的话只返回
数组中所开列的协议信息。
lpProtocolBuffer:一个用PROTOCOL_INFO结构填充的缓冲区。参见下文中对PROTOCOL_INFO结构的具体描述。
lpdwBufferLength:输入时,存有传递给WSAEnumProtocols()函数的lpProtocolBuffer缓冲区长度。输出时,表示为获取所有信息需传递给WSAEnumProtocols()函数的缓冲区长度。本函数不能重复调用;传入的缓冲区必须足够大以能存放所有的元素。这个规定降低了该函数的复杂度。由于一个机器上装载的协议数目往往是很小的,所以并不会产生问题。
返回值:若无错误发生,WSAEnumProtocols()返回协议的数目。否则的话,将返回INVALID_SOCKET错误,应用程序可通过WSAGetLastError()来获取相应的
错误代码。
14.gethostbyname()返回对应于给定
主机名的包含主机名字和地址信息的hostent结构
指针。结构的声明与gethostaddr()中一致。
#include<winsock2.h>
structhostentFAR*PASCALFARgethostbyname(constcharFAR*name); name:指向主机名的指针。
返回类型
structhostent
{
charFAR*h_name;
charFAR*FAR*h_aliases;
shorth_addrtype;
shorth_length;
charFAR*FAR*h_addr_list;
};
15.gethostbyaddr()返回对应于给定地址的包含主机名字和地址信息的
hostent结构
指针。