📄 teach_sp_44.htm
字号:
<p>可以看出来信号灯的使用方式和互斥量的使用方式非常相似,下面的代码使用初始值为2的信号灯来保证只有两个线程可以同时进行数据库调用:
<pre>
DWORD threadA(void* pD)
{
int iID=(int)pD;
//在内部重新打开
HANDLE hCounterIn=OpenSemaphore(SEMAPHORE_ALL_ACCESS,FALSE,"sam sp 44");
for(int i=0;i<3;i++)
{
printf("%d wait for object\n",iID);
WaitForSingleObject(hCounterIn,INFINITE);
printf("\t\tthread %d : do database access call\n",iID);
Sleep(100);
printf("\t\tthread %d : do database access call end\n",iID);
ReleaseSemaphore(hCounterIn,1,NULL);
}
CloseHandle(hCounterIn);
return 0;
}
//in main function
{
//创建信号灯
HANDLE hCounter=NULL;
if( (hCounter=OpenSemaphore(SEMAPHORE_ALL_ACCESS,FALSE,"sam sp 44"))==NULL)
{
//如果没有其他进程创建这个信号灯,则重新创建
hCounter = CreateSemaphore(NULL,2,2,"sam sp 44");
}
//创建线程
HANDLE hThread[3];
CWinThread* pT1=AfxBeginThread((AFX_THREADPROC)threadA,(void*)1);
CWinThread* pT2=AfxBeginThread((AFX_THREADPROC)threadA,(void*)2);
CWinThread* pT3=AfxBeginThread((AFX_THREADPROC)threadA,(void*)3);
hThread[0]=pT1->m_hThread;
hThread[1]=pT2->m_hThread;
hThread[2]=pT3->m_hThread;
//等待线程结束
WaitForMultipleObjects(3,hThread,TRUE,INFINITE);
//关闭句柄
CloseHandle(hCounter);
}
</pre>
<p>信号灯有时用来作为计数器使用,一般来讲将其初始值设置为0,先调用ReleaseSemaphore来增加其计数,然后使用WaitForSingleObject来减小其计数,遗憾的是通常我们都不能得到信号灯的当前值,但是可以通过设置WaitForSingleObject的等待时间为0来检查信号灯当前是否为0。
<p><a name=event></a>接下来我们讲最后一种同步对象:事件,前面讲的信号灯和互斥量可以保证资源被正常的分配和使用,而事件是用来通知其他进程/线程某件操作已经完成。例如:现在有三个线程:threadA,threadB,threadC,现在要求他们中的部分功能要顺序执行,也就是说threadA执行完一部分后threadB执行,threadB执行完一部分后threadC开始执行。也许你觉得下面的代码可以满足要求:
<pre><a name=ana_sample></a>要求:A1执行完后执行B2然后执行C3,再假设每个任务的执行时间都为1,而且允许并发操作。
方案一:
dword threadA(void*)
{
do something A1;
create threadB;
do something A2;
do something A3;
}
dword threadB(void*)
{
do something B1;
do something B2;
create threadC;
do something B3;
}
dword threadC(void*)
{
do something C1;
do something C2;
do something C3;
}
方案二:
dword threadA(void*)
{
do something A1;
do something A2;
do something A3;
}
dword threadB(void*)
{
do something B1;
wait for threadA end
do something B2;
do something B3;
}
dword threadC(void*)
{
do something C1;
do something C2;
wait for threadB end
do something C3;
}
main()
{
create threadA;
create threadB;
create threadC;
}
方案三:
dword threadA(void*)
{
do something A1;
release event1;
do something A2;
do something A3;
}
dword threadB(void*)
{
do something B1;
wait for envet1 be released
do something B2;
release event2;
do something B3;
}
dword threadC(void*)
{
do something C1;
do something C2;
wait for event2 be released
do something C3;
}
main()
{
create threadA;
create threadB;
create threadC;
}
比较一下三种方案的执行时间:
方案一 方案二 方案三
1 threadA threadB threadC threadA threadB threadC threadA threadB threadC
2 A1 A1 B1 C1 A1 B1 C1
3 A2 B1 A2 C2 A2 B2 C2
4 A1 B2 A3 A3 B3 C3
5 B3 C1 B2
6 C2 B3
7 C3 C3
8
</pre>
<p>可以看出来方案三的执行时间是最短的,当然这个例子有些极端,但我们可以看出事件对象用于通知其他进程/线程某件操作已经完成方面的作用是很大的,而且如果有的任务要在进程尖进行协调采用等待其他进程中线程结束的方式是不可能实现的。此外我也希望通过这个例子讲一点关于分析线程执行效率的方法。
<p>事件对象可以一两种方式创建,一种为自动重置,在其他线程使用WaitForSingleObject等待到事件对象变为有信号后该事件对象自动又变为无信号状态,一种为人工重置在其他线程使用WaitForSingleObject等待到事件对象变为有信号后该事件对象状态不变。例如有多个线程都在等待一个线程运行结束,我们就可以使用人工重置事件,在被等待的线程结束时设置该事件为有信号状态,这样其他的多个线程对该事件的等待都会成功(因为该事件的状态不会被自动重置)。事件相关的API如下:
<pre>
创建事件对象:
HANDLE CreateEvent(
LPSECURITY_ATTRIBUTES lpEventAttributes,// 安全属性,NULL表示使用默认的安全描述
BOOL bManualReset, // 是否为人工重置
BOOL bInitialState, // 初始状态是否为有信号状态
LPCTSTR lpName // 名字
);
打开事件对象:
HANDLE OpenEvent(
DWORD dwDesiredAccess, // 存取方式
BOOL bInheritHandle, // 是否能够被继承
LPCTSTR lpName // 名字
);
设置事件为无信号状态:
BOOL ResetEvent(
HANDLE hEvent // 句柄
);
设置事件有无信号状态:
BOOL SetEvent(
HANDLE hEvent // 句柄
);
关闭事件对象:
BOOL CloseHandle(
HANDLE hObject // 句柄
);
</pre>
<p>下面的代码演示了自动重置和人工重置事件在使用中的不同效果:
<pre>
DWORD threadA(void* pD)
{
int iID=(int)pD;
//在内部重新打开
HANDLE hCounterIn=OpenEvent(EVENT_ALL_ACCESS,FALSE,"sam sp 44");
printf("\tthread %d begin\n",iID);
//设置成为有信号状态
Sleep(1000);
SetEvent(hCounterIn);
Sleep(1000);
printf("\tthread %d end\n",iID);
CloseHandle(hCounterIn);
return 0;
}
DWORD threadB(void* pD)
{//等待threadA结束后在继续执行
int iID=(int)pD;
//在内部重新打开
HANDLE hCounterIn=OpenEvent(EVENT_ALL_ACCESS,FALSE,"sam sp 44");
if(WAIT_TIMEOUT == WaitForSingleObject(hCounterIn,10*1000))
{
printf("\t\tthread %d wait time out\n",iID);
}
else
{
printf("\t\tthread %d wait ok\n",iID);
}
CloseHandle(hCounterIn);
return 0;
}
//in main function
{
HANDLE hCounter=NULL;
if( (hCounter=OpenEvent(EVENT_ALL_ACCESS,FALSE,"sam sp 44"))==NULL)
{
//如果没有其他进程创建这个事件,则重新创建,该事件为人工重置事件
hCounter = CreateEvent(NULL,TRUE,FALSE,"sam sp 44");
}
//创建线程
HANDLE hThread[3];
printf("test of manual rest event\n");
CWinThread* pT1=AfxBeginThread((AFX_THREADPROC)threadA,(void*)1);
CWinThread* pT2=AfxBeginThread((AFX_THREADPROC)threadB,(void*)2);
CWinThread* pT3=AfxBeginThread((AFX_THREADPROC)threadB,(void*)3);
hThread[0]=pT1->m_hThread;
hThread[1]=pT2->m_hThread;
hThread[2]=pT3->m_hThread;
//等待线程结束
WaitForMultipleObjects(3,hThread,TRUE,INFINITE);
//关闭句柄
CloseHandle(hCounter);
if( (hCounter=OpenEvent(EVENT_ALL_ACCESS,FALSE,"sam sp 44"))==NULL)
{
//如果没有其他进程创建这个事件,则重新创建,该事件为自动重置事件
hCounter = CreateEvent(NULL,FALSE,FALSE,"sam sp 44");
}
//创建线程
printf("test of auto rest event\n");
pT1=AfxBeginThread((AFX_THREADPROC)threadA,(void*)1);
pT2=AfxBeginThread((AFX_THREADPROC)threadB,(void*)2);
pT3=AfxBeginThread((AFX_THREADPROC)threadB,(void*)3);
hThread[0]=pT1->m_hThread;
hThread[1]=pT2->m_hThread;
hThread[2]=pT3->m_hThread;
//等待线程结束
WaitForMultipleObjects(3,hThread,TRUE,INFINITE);
//关闭句柄
CloseHandle(hCounter);
}
</pre>
<p>从执行结果中我们可以看到在第二次执行时由于使用了自动重置事件threadB中只有一个线程能够等待到threadA中释放的事件对象。
<p>在处理多进程/线程的同步问题时必须要小心避免发生死锁问题,比如说现在有两个互斥量A和B,两个线程tA和tB,他们在执行前都需要得到这两个互斥量,但现在这种情况发生了,tA拥有了互斥量A,tB拥有了互斥量B,但它们同时都在等待拥有另一个互斥量,这时候显然谁也不可能得到自己希望的资源。这种互相拥有对方所拥有的资源而且都在等待对方拥有的资源的情况就称为死锁。关于这个问题更详细的介绍请参考其他参考书。
<p>在MFC中对于各种同步对象都提供了相对应的类<br>
<img src=sp_teach_441.gif><br>
<font color=red>在这些类中封装了上面介绍的对象创建,打开,控制,删除功能。但是如果要使用等待功能则需要使用另外两个类:CSingleLock和CMultiLock。这两个类中封装了WaitForSingleObject和WaitForMultipleObjects函数。如果大家觉的需要可以看看这些类的定义,我想通过上面的介绍可以很容易理解,但是在对象同步问题上我觉得使用API函数比使用MFC类更为直观和方便。</font>
<p><a href=sam_sp_44.zip>下载本节示范代码 25K</a>
<p>最后我留一个问题给大家吧,这个问题是一个比较经典的同步问题:一个写/多个读,规则如下:
<ol>
<li>一个人在写时,其他人不允许写。</li>
<li>一个人在写时,其他人不允许读。</li>
<li>一个人在读时,其他人不允许写。</li>
<li>一个人在读时,其他人允许读。</li>
</ol>
如果谁有兴趣在做完后可以将代码和方法<a href=mailto:wyy_cq@21cn.com>邮寄给我</a>在本站发表。
<p align=center><a href=index.htm#charpter4>返回</a></p>
<p align=center><small>版权所有 闻怡洋 <a href=http://www.vchelp.net/><font class=engul>http://www.vchelp.net/<font></font></font></a></small></p>
</small>
</td></tr><!-- article content end-->
</table>
</td>
</tr><!-- article title end-->
</table>
</td></tr></table><!--整体框架 END-->
<!-- LANGUAGE='JavaScript'>write_tail();<-->
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -