现在的位置: 首页 > 综合 > 正文

浅谈线程同步

2013年02月22日 ⁄ 综合 ⁄ 共 2477字 ⁄ 字号 评论关闭

现代操作系统都支持多线程操作了,多线程操作带来的一个麻烦就是多个线程对共享数据的访问。假设我们有线程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将

     同步对象设为“未标志的”,并对共享资源进行访问,访问结束后再将同步对象设为“标志的”使得其它线程可以访问

     共享资源。

为了便于理解线程同步的过程,我们可以把我们需要访问的共享资源当成是一件放在房间里的东西,而同步对象当成

是门上的锁,而需要访问资源的线程就可以当做是取东西的人了,“标志的”状态表示门是开的,“未标志的”状态表示

门是锁着的,而此时钥匙在进去的那个人手里。当某人进入房间后,就将门锁上,其他人就无法进入了,只有等这个

人出来之后才能进入。


下面是一个我自己写的利用事件对象来同步访问共享内存实例:

#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

先执行,它得到事件对象:hEvent,但是如果正好这时它的时间片终止了,于是轮到线程ThreadForRead执行,但因

为现在在线程ThreadForWrite中,ResetEvent函数还没有被执行,所以该事件对象仍然处于“标志的”状态,因此线程

ThreadForRead就可以得到该事件对象,也就是说,此时两个线程都可以访问共享资源,于是结果就无法预料了。


第二个问题,当把这段程序移植到多CPU平台上时,两个线程就可以同时运行,这时再主函数里调用SetEvent函数将

其设置为”标志的“状态已经没多大意义了,因为这两个线程都已经可以访问共享资源了,而且是同时使用。


看来以后实现线程同步时还是老实点使用自动重置的事件对象。


抱歉!评论已关闭.