C#读者写者问题:互斥失败了!

ejiue 2004-05-11 11:43:07
在C#里使用Mutex解决读者写者问题,运行时某个读者线程在调用ReleaseMutex()方法导致了如下异常:
“未处理的“System.ApplicationException”类型的异常出现在 mscorlib.dll 中。”
查看MSDN,原因为“调用线程不拥有互斥体”。
奇怪了,我并没有进行释放两次的操作。

另外,控制台输出显示:当有读者正在读的时候,写者竟然开始写了。
我调试时,发现最后一个读线程还没有解锁,写线程就开始输出了。

代码如下(读者优先):

private static Mutex rc = new Mutex();
private static Mutex wsem = new Mutex();
private static int readercount = 0;

private static void ReaderThreadProc_ReaderPriority()
{
string threadname = Thread.CurrentThread.Name;
int id = int.Parse(threadname);
int delay = Convert.ToInt32(GetThreadDelay(id)*1000);
int persist = Convert.ToInt32(GetThreadPersist(id)*1000);

Thread.Sleep(delay);
Console.WriteLine("{0} is requesting for reading",threadname);

rc.WaitOne();
readercount++;
if ( readercount == 1 )
{
wsem.WaitOne();
}
rc.ReleaseMutex();

Console.WriteLine("{0} is beginning reading",threadname);
Thread.Sleep(persist);
Console.WriteLine("{0} is ending reading",threadname);

try
{
rc.WaitOne();
readercount--;
if ( readercount == 0 )
{
wsem.ReleaseMutex();//调试发现这一行还没有解锁,写者竟然输出
}
rc.ReleaseMutex();//这一行在某个时刻会导致异常。
}
catch
{
;
}
}

private static void WriterThreadProc_ReaderPriority()
{
string threadname = Thread.CurrentThread.Name;
int id = int.Parse(threadname);
int delay = Convert.ToInt32(GetThreadDelay(id)*1000);
int persist = Convert.ToInt32(GetThreadPersist(id)*1000);

Thread.Sleep(delay);
Console.WriteLine("{0} is requesting for writing",threadname);

wsem.WaitOne();
//调试发现,某个时刻最后一个读者还未RleaseMutex,
//这里竟然先执行Console.WriteLine了。
//然后读者才执行RleaseMutex
Console.WriteLine("{0} is beginning writing",threadname);
Thread.Sleep(persist);
Console.WriteLine("{0} is ending writing",threadname);
wsem.ReleaseMutex();
}

郁闷。请朋友们帮我看看问题出在哪里?

...全文
220 25 打赏 收藏 转发到动态 举报
写回复
用AI写文章
25 条回复
切换为时间正序
请发表友善的回复…
发表回复
ejiue 2004-05-14
  • 打赏
  • 举报
回复
这几天为了解决这个问题,查了很多方面的资料。
MSDN、Google上都没有找到Monitor实现Semaphore的相关说明。
希望这个帖子能给和我一样遇到这个问题的朋友一点帮助。

谢谢大家,揭贴。
ejiue 2004-05-14
  • 打赏
  • 举报
回复
查了一下Modern Operating System,原来Monitor是“管程”的意思。
由于管程比信号量高级,所有C#没有提供专门的信号量。
根据管程的定义和原语等价,我自己写了一个Semaphore类。

using System;
using System.Threading;

namespace Reader_Writer
{
/// <summary>
/// Semaphore。
/// </summary>
public class Semaphore
{
private object mx;
private int counter;

public Semaphore()
{
counter = 1;
mx = new object();
}

public void Down()
{
Monitor.Enter(mx);
counter--;
if ( counter < 0 )
{
Monitor.Wait(mx);
}
Monitor.Exit(mx);
}
public void Up()
{
Monitor.Enter(mx);
counter++;
if ( counter <= 0 )
{
Monitor.Pulse(mx);
}
Monitor.Exit(mx);
}
}
}

正确的读者-写者实现:
private static Semaphore rsem = new Semaphore();
private static Semaphore wsem = new Semaphore();
private static int readercount = 0;

private static void ReaderThreadProc_ReaderPriority()
{
string threadname = Thread.CurrentThread.Name;
int id = int.Parse(threadname);
int delay = Convert.ToInt32(GetThreadDelay(id)*1000);
int persist = Convert.ToInt32(GetThreadPersist(id)*1000);

Thread.Sleep(delay);
Console.WriteLine("{0} is requesting for reading",threadname);

rc.WaitOne();
readercount++;
if ( readercount == 1 )
{
wsem.Down();
}
rc.ReleaseMutex();

Console.WriteLine("{0} is beginning reading",threadname);
Thread.Sleep(persist);
Console.WriteLine("{0} is ending reading",threadname);

rc.WaitOne();
readercount--;
if ( readercount == 0 )
{
wsem.Up();
}
rc.ReleaseMutex();
}

private static void WriterThreadProc_ReaderPriority()
{
Thread.Sleep(delay);
Console.WriteLine("{0} is requesting for writing",threadname);

wsem.Down();
Console.WriteLine("{0} is beginning writing",threadname);
Thread.Sleep(persist);
Console.WriteLine("{0} is ending writing",threadname);
wsem.Up();
}

小结:
1、Mutex由于存在所有权的问题,必须由所有者释放,这和P、V原语是不能等同的。
在实际使用时要小心。
2、用ReaderWriterLock实现读者-写者比较简单,但是只能实现互斥,
不能实现读者优先或写者优先的特殊要求,因此灵活性较差。
3、Monitor,即管程,是比较高级的概念。但是和P、V原语是可以等价的。
我用它来实现了信号量(Semaphore),并成功的解决了读者-写者问题。
4、Semaphore,信号量,C#没有直接提供,但可以用Monitor实现。
相当于MFC里的CriticalSection。非常好用。

ejiue 2004-05-13
  • 打赏
  • 举报
回复
无论是Monitor也好,Mutex也好,都要求同一线程执行Enter、Exit或Wait、Release。
我现在需要象EnterCriticalSection、LeaveCriticalSection那样的,不需要由同一个
线程执行,并且当某个线程LeaveCriticalSection以后,阻塞在EnterCriticalSection
上的线程能马上得到通知的机制。

Monitor的Wait、Pulse又必须写在Enter、Exit内,而且语义和P、V原语不大一致。
想不懂.net为什么不沿用原来的机制。
ejiue 2004-05-12
  • 打赏
  • 举报
回复
to liu_z_j():

你说的对。Mutex有个所有权问题,只有拥有者才能释放,所以我的代码出错。
我正在尝试用Monitor...

liu_z_j 2004-05-12
  • 打赏
  • 举报
回复
当两个或更多线程需要同时访问一个共享资源时,系统需要使用同步机制来确保一次只有一个线程使用该资源。Mutex 是同步基元,它只向一个线程授予对共享资源的独占访问权。如果一个线程获取了互斥体,则要获取该互斥体的第二个线程将被挂起,直到第一个线程释放该互斥体。

你用了两个互斥体能不出错吗!?
“调用线程不拥有互斥体”异常也是正确的,你没有调用互斥体的WaitOne方法就调用ReleaseMutex方法就是这个结果啊!
ejiue 2004-05-12
  • 打赏
  • 举报
回复
Monitor.Wait、Monitor.Pulse和P、V原语好象是不同的。
Wait()直接就把线程移到等待队列了,而P原语有一个判断。
ejiue 2004-05-12
  • 打赏
  • 举报
回复
to greatsft(C的使者):

thanks。
Monitor相当于CriticalSection?
如何在C#中实现象MFC中的EnterCriticalSection、LeaveCriticalSection的功能?
我需要的正是这个。
greatsft 2004-05-12
  • 打赏
  • 举报
回复
你要实现什么?Monitor的wait()和pulse()就是实现原语p,v操作的
ejiue 2004-05-12
  • 打赏
  • 举报
回复
刚才使用Monitor试了一下,输出的读写流程不正确。
看了一本SDK的书,说Win32的Mutex不能用来锁定这里的wsem。这和.NET有什么关联没有?

greatsft 2004-05-12
  • 打赏
  • 举报
回复
to ejiue(猪慕狼犸)
的确象你说的p,v操作是有一个判断,但是我的意思是在c#中调用wait()和pulse()
要实现的功能就是和操作系统中用p,v来互斥操作一样

to 楼主
你说的mfc中的两个方法我没用过,不过你可以说说你想实现的具体功能,不过
按照你上面的描述,我说的方法肯定行
ejiue 2004-05-12
  • 打赏
  • 举报
回复
to redbb:

ReaderWriterLock能实现互斥,但是可控性太差。
无法实现读/写者优先的要求。

谁能介绍一下如何用Monitor替代MFC中的EnterCriticalSection和LeaveCriticalSection。
我用Monitor.Enter和Monitor.Exit程序运行时死锁。
vzxq 2004-05-12
  • 打赏
  • 举报
回复
学习,帮你UP
elite2018 2004-05-12
  • 打赏
  • 举报
回复
try readWriteLock
ReinSelf 2004-05-12
  • 打赏
  • 举报
回复
up.
ejiue 2004-05-11
  • 打赏
  • 举报
回复
To redbb(....Dotneter....):

不是已经加了mutex锁了?
elite2018 2004-05-11
  • 打赏
  • 举报
回复
try to add a lock
greatsft 2004-05-11
  • 打赏
  • 举报
回复
to 楼主:
如果mutex不行的话,建议尝试下Monitor的静态方法Enter()和Exit()结合静态方法Wait()
和Pulse()一样可以实现
比如说:
using System.Threading;
...
public class Test{
...
private int i=1;
public void Circle(){
Monitor.Enter(this.i); //同步锁住这段代码区
for(int i=0;i<10;i++){
Monitor.Wait(this.i);
Console.Write("Circle has been woken up!");
Monitor.Pulse(this.i);
}
Monitor.Exit(this.i);
}
public void noCircle(){
Monitor.Enter(this.i)
for(int i=0;i<10;i++){
Monitor.Pulse(this.i); //唤醒
Console.Write("noCircle is running");
Monitor.Wait(this.i); //等待
Monitor.Pulse(this.i);
Console.Write("noCircle has been woken up!");
}
Monitor.Exit(this.i);
}
...
}
运行结果是:
noCircle is running
Circle has been woken up!
noCircle has been woken up!
...

注意:wait()和pulse()只能用在Enter()和Exit()锁定的代码中!
这样实现的功能就是操作系统中的原语p,v操作

wish u good luck
Greatsft
ejiue 2004-05-11
  • 打赏
  • 举报
回复
help.
magiclamp 2004-05-11
  • 打赏
  • 举报
回复
算法是对的呀。
liu_z_j 2004-05-11
  • 打赏
  • 举报
回复
你这样写只能保证多个线程读的时候只有一个线程读文件和多个线程写的时候只有一个线程写文件啊!
应该用同一个Mutex在读和写的线程中!
加载更多回复(5)

110,568

社区成员

发帖
与我相关
我的任务
社区描述
.NET技术 C#
社区管理员
  • C#
  • Web++
  • by_封爱
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

让您成为最强悍的C#开发者

试试用AI创作助手写篇文章吧