浅谈线程同步
现代操作系统都支持多线程操作了,多线程操作带来的一个麻烦就是多个线程对共享数据的访问。假设我们有线程A
和线程B,它们需要访问同一内存区域,线程A写,线程B读。一般情况下我们是希望线程A写操作完成后再进行读操
作或者线程B读操作完成后我们再进行写操作。但是在多线程中,可能由于线程A分配的时间片用完了或者其他原因导
致线程A的写操作还没完成就调用线程B来对这块共享内存进行读操作,也有可能在线程B的读操作还没完成就调用线
程A来对这块共享内存进行写操作,这些情况都有可能导致严重的逻辑错误。为了解决这一现象,我们就需要一种机
制使得各个线程能够协同工作,这就是我们所讲的线程同步机制了。
Windows系统中用于线程同步的常用机制有:
互斥对象(Mutex)
事件对象(Event)
信号量(Semaphore)
临界区(critical section)
可等待计时器(Waitable Timer)
在学习线程同步之前,我们需要先来了解下同步过程中最重要的两个概念:同步对象和等待函数。
同步对象主要有(Mutex、Event、Semaphore、critical section)。同步对象一般具有两种状态:标志的和未标志的。线
程根据是否已经完成操作将同步对象设置为标志的或未标志的。
而等待函数的功能是专门用于等待同步对象状态改变。一个线程调用等待函数后执行会暂停,直到同步对象的状态变
为标志的之后,等待函数才会返回,线程才能继续执行下去。
关于上面所讲的几种主要同步对象的概念,这篇临界区,互斥量,信号量,事件的区别讲的很详细,不懂的朋友可以
去肯看。
线程同步的过程:
1、在需要进行线程同步的进程中定义某种同步对象,同步对象必需是全局的,以保证需要同步的所有线程都可以访
问到同步对象。
2、开始时,所有的线程相互独立地运行
3、当某一线程(为了方便描述设为线程A)需要访问共享资源时,若同步对象为“未标志的”,继续等待;反之,线程A将
同步对象设为“未标志的”,并对共享资源进行访问,访问结束后再将同步对象设为“标志的”使得其它线程可以访问
共享资源。
为了便于理解线程同步的过程,我们可以把我们需要访问的共享资源当成是一件放在房间里的东西,而同步对象当成
是门上的锁,而需要访问资源的线程就可以当做是取东西的人了,“标志的”状态表示门是开的,“未标志的”状态表示
门是锁着的,而此时钥匙在进去的那个人手里。当某人进入房间后,就将门锁上,其他人就无法进入了,只有等这个
人出来之后才能进入。
下面是一个我自己写的利用事件对象来同步访问共享内存实例:
[cpp]
#include <windows.h>
#include <stdio.h>
#include <string.h>
TCHAR szSharedBuffer[100] = {0}; //共享内存
HANDLE hEvent; //事件对象句柄
DWORD WINAPI ThreadForWrite (LPVOID lpParam);
DWORD WINAPI ThreadForRead (LPVOID lpParam);
int main()
{
HANDLE hWrite;
HANDLE hRead;
hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
hWrite = CreateThread(NULL,
0,
ThreadForWrite,
0,
0,
NULL);
hRead = CreateThread(NULL,
0,
ThreadForRead,
0,
0,
NULL);
SetEvent(hEvent);
while(1);
return 0;
}
DWORD WINAPI ThreadForWrite(LPVOID lpParam)
{
while (1)
{
WaitForSingleObject(hEvent, INFINITE);
printf("Please input the shared chars: ");
scanf("%s", szSharedBuffer);
SetEvent(hEvent);
}
return 0;
}
DWORD WINAPI ThreadForRead(LPVOID lpParam)
{
while (1)
{
WaitForSingleObject(hEvent, INFINITE);
if (!strlen(szSharedBuffer))
printf("The shared chars is null now!\n");
else
printf("The shared chars is %s\n", szSharedBuffer);
SetEvent(hEvent);
}
return 0;
}
#include <windows.h>
#include <stdio.h>
#include <string.h>
TCHAR szSharedBuffer[100] = {0}; //共享内存
HANDLE hEvent; //事件对象句柄
DWORD WINAPI ThreadForWrite (LPVOID lpParam);
DWORD WINAPI ThreadForRead (LPVOID lpParam);
int main()
{
HANDLE hWrite;
HANDLE hRead;
hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
hWrite = CreateThread(NULL,
0,
ThreadForWrite,
0,
0,
NULL);
hRead = CreateThread(NULL,
0,
ThreadForRead,
0,
0,
NULL);
SetEvent(hEvent);
while(1);
return 0;
}
DWORD WINAPI ThreadForWrite(LPVOID lpParam)
{
while (1)
{
WaitForSingleObject(hEvent, INFINITE);
printf("Please input the shared chars: ");
scanf("%s", szSharedBuffer);
SetEvent(hEvent);
}
return 0;
}
DWORD WINAPI ThreadForRead(LPVOID lpParam)
{
while (1)
{
WaitForSingleObject(hEvent, INFINITE);
if (!strlen(szSharedBuffer))
printf("The shared chars is null now!\n");
else
printf("The shared chars is %s\n", szSharedBuffer);
SetEvent(hEvent);
}
return 0;
}
当然这里只是对线程同步进行一个简单的说明,真正要掌握线程同步比这里所写的要复杂的多。这将在以后的学习中
慢慢补充。
补充:
在上面这个例子中,创建事件对象时,第二个参数我设置的是FALSE,也就是说将事件自动重置。后来我自己改用
设置为TRUE,结果出了问题。后来想起来设置为TRUE的话,需要我们手动设置。于是在WaitForSingleObject函数
后面加了ResetEvent函数。本以为这样就可以解决问题了,但还是有问题。后来在网上问了下别人也找了点书看才知
道了原因。
这种做法存在两个问题,一个问题是,在单CPU平台下,同一时刻只能有一个线程在运行,假设线程ThreadForWrite
补充:软件开发 , C++ ,