线程同步
多个线程需要访问同一个资源时,你应该考虑同步问题;例如:银行系统中,可能使用多个线程对数据
库进行操作,如:存储、更新、查询线程同时访问数据库,极可能出现:查询到的数据是未完全更新的
数据。还有就是火车售票系统,多窗口售票可以认为是多个线程,多窗口共享总票资源(tickets),此时
如果不对共享资源进行同步处理,就会出现:卖第0张票的情况!
long tickets = 100; //一共100张票
unsigned _stdcall ThreadProc1(void * pArgu = NULL)
{
while(TRUE)
{
if(tickets>0)
{
FILE * fp = fopen("c://ticketseller.txt", "a");
fprintf(fp, "A窗口出票:%d/n", tickets--);
fclose(fp);
}
else
{
FILE * fp = fopen("c://ticketseller.txt", "a");
fprintf(fp, "A窗口票卖完了:%d/n", tickets);
fclose(fp);
break;
}
}
return 0;
}
unsigned _stdcall ThreadProc2(void * pArgu = NULL)
{
while(TRUE)
{
if(tickets>0)
{
Sleep(1); //B售票员打个顿,停了1毫秒(0.001秒)
FILE * fp = fopen("c://ticketseller.txt", "a");
fprintf(fp, "B窗口出票:%d/n", tickets--);
fclose(fp);
}
else
{
FILE * fp = fopen("c://ticketseller.txt", "a");
fprintf(fp, "B窗口票卖完了:%d/n", tickets);
fclose(fp);
break;
}
}
return 0;
}
#include "process.h"
void CMy1006Dlg::OnOK()
{
unsigned nThreadID = 0;
FILE * fp = fopen("c://ticketseller.txt", "w");
fprintf(fp, "开始测试多线程卖票:%d/n", tickets);
fclose(fp);
_beginthreadex(NULL,
0,
ThreadProc1,
NULL,
0,
&nThreadID);
_beginthreadex(NULL,
0,
ThreadProc2,
NULL,
0,
&nThreadID);
}
//上述代码,你可能会看到下面你不愿意看到的结果:
开始测试多线程卖票:100
A窗口出票:100
A窗口出票:99
B窗口出票:98
A窗口出票:97
A窗口出票:96
A窗口出票:95
A窗口出票:94
A窗口出票:93
B窗口出票:92
A窗口出票:91
A窗口出票:90
A窗口出票:89
A窗口出票:88
A窗口出票:87
B窗口出票:86
A窗口出票:85
A窗口出票:84
B窗口出票:83
A窗口出票:82
A窗口出票:81
A窗口出票:80
A窗口出票:79
A窗口出票:78
A窗口出票:77
B窗口出票:76
A窗口出票:75
A窗口出票:74
A窗口出票:73
A窗口出票:72
A窗口出票:71
B窗口出票:70
A窗口出票:69
A窗口出票:68
A窗口出票:67
A窗口出票:66
A窗口出票:65
A窗口出票:64
B窗口出票:63
A窗口出票:62
A窗口出票:61
A窗口出票:60
B窗口出票:59
A窗口出票:58
A窗口出票:57
A窗口出票:56
A窗口出票:55
A窗口出票:54
A窗口出票:53
B窗口出票:52
A窗口出票:51
A窗口出票:50
A窗口出票:49
A窗口出票:48
A窗口出票:47
B窗口出票:46
A窗口出票:45
A窗口出票:44
A窗口出票:43
A窗口出票:42
A窗口出票:41
B窗口出票:40
A窗口出票:39
A窗口出票:38
B窗口出票:37
A窗口出票:35
A窗口出票:34
A窗口出票:33
A窗口出票:32
A窗口出票:31
B窗口出票:30
A窗口出票:29
A窗口出票:28
A窗口出票:27
A窗口出票:26
A窗口出票:25
B窗口出票:24
A窗口出票:23
A窗口出票:22
A窗口出票:21
A窗口出票:20
A窗口出票:19
A窗口出票:18
B窗口出票:17
A窗口出票:16
A窗口出票:15
A窗口出票:14
A窗口出票:13
B窗口出票:12
A窗口出票:11
A窗口出票:10
A窗口出票:9
A窗口出票:8
A窗口出票:7
A窗口出票:5
A窗口出票:4
A窗口出票:3
A窗口出票:2
A窗口出票:1
A窗口票卖完了:0
B窗口出票:0
B窗口票卖完了:-1
解决办法很多,在Windows平台下MFC为我们封装了同步管理类:CSyncObject,我们经常看到的CEvent(
事件)、CCriticalSection(临界区)、CMutex(互斥体)、CSemaphore(信号量)都是它的派生类; 使用它们
解决同步问题就方便多了!它们的头文件都是:#include "afxmt.h"
一、事件(CEvent)
常用于不同线程之间在满足特定条件情况下的唤醒操作;它有两种状态,有信号和无信号状态;例如:
在一些串口通信程序中,一个线程负责监听串口状态,一个线程负责数据的界面回显;那么就可以在侦
听到串口状态发生变化之后,用信号的形式通知界面回显线程;
我经常这么干:
CEvent hUIEvent = CreateEvent(NULL, FALSE, TRUE, NULL );
//创建一个使用默认安全属性、初始无信号、手动状态、无名称的事件对象(界面线程触发事件)
侦听到串口状态发生变化时:
//...数据处理
SetEvent(hUIEvent); //通知界面回显线程,设置事件为有信号状态;ResetEvent(hUIEvent); 设置为
无信号状态
//界面回显线程
unsigned _stdcall UIThreadProc(void * pArgus = NULL)
{
DWORD dwWait = WaitForSingleObject(hUIEvent, 3000); //3秒检测一次事件状态
switch(dwWait)
{
case WAIT_OBJECT_0:
//界面数据显示
break;
case WAIT_TIMEOUT:
break;
case WAIT_FAILED:
break;
default:
break;
}
return 0;
}
二、临界区(CCriticalSection,其构造函数没有参数)
当多个线程访问一个独占性共享资源时,可以使用“临界区”对象。任一时刻只有一个线程可以拥有
临界区对象,拥有临界区的线程可以访问被保护起来的资源或代码段,其他希望进入临界区的线程将全
部被挂起等待,直到拥有临界区的线程放弃临界区时为止,这样就保证了不会在同一时刻出现多个线程
访问共享资源。
CCriticalSection criticalSection;
criticalSection.Lock();
//...被保护的代码段
criticalSetion.Unlock();
注意:上面提到的火车售票问题,由于tickets不是独占性资源,就不要使用临界区对象;一旦一个售票
窗口得到临界资源,他不卖票反而吃饭去了,其他窗口则有票不能卖!一定要注意了,是独占性资源可以
使用临界区对象,这个资源我使用了,别人都别想用;
三、互斥体(CMutex)
和临界区类似,不同的是:互斥体可应用于不同进程之间;如,我们常使用互斥体对象实现应用程序的
唯一实例限制(不同进程之间):
首先在App类中定义互斥体对象 HANDLE hMutex;
在App::InitInstance的适当位置,添加如下代码:
hMutex = CreateMutex(NULL, TRUE, "CUSTOM_MUTEX_NAME");
if( hMutex != NULL )
{
if( GetLastError() == ERROR_ALREADY_EXISTS )
{
exit(0);
}
}
但好像一些较复杂的应用程序,可能也会不尽人意,具体什么情况忘记了,以后想起来了再补充;但我
想这种方法已经不错了!
关于临界区和互斥体说一个简单的例子:
大家都做过火车,火车厕所的使用相当于一个临界区资源,具有独占性,当一个人进去开始使用资源之前,他首先要将厕所的门锁上,使用完资源后,再将锁打开;特别是春运或5.1/10.1放假时,坐硬座的乘客应该深有体会!别人加锁之后,你只有等待!
四、信号量
常用于对访问某一共享资源线程数目的限制;常用到的Win32函数有:
CreateSemaphore(NULL, 10, 10, NULL);
//创建一个使用默认安全属性、初始线程计数是10、线程总数为10、无名称的信号量对象