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

一个线程阻塞的读写对象 .NET

2012年01月05日 ⁄ 综合 ⁄ 共 19227字 ⁄ 字号 评论关闭

 先说一说这个需求:
我有一个公共对象,(可能会拓展成一堆),有许多线程需要访问它,操作方式包括读取和修改两种,
我涉及到一个同步问题,就是,当有线程读取时,其他的读线程可以正常访问,而写线程需要阻塞等待,
到无线程继续读时,才能开始写(当然他阻塞的时候,不能允许新读线程进入),
而当写线程在访问对象时,其他的读和写线程都需要被阻塞.
我觉得这个问题比较难的地方就是,有时候是需要互斥所有线程,有时候只互斥写线程,
如果是互斥所有线程,我们的对象设计可以这么简单就实现.

view plaincopy to clipboardprint?
using System;  
using System.Collections.Generic;  
using System.Text;  
namespace ReadWriteObj  
{  
    class MutexObj<T>  
    {  
        private object m_lock;  
        private T m_value;  
        public MutexObj(T val)  
        {  
            m_value = val;  
            m_lock = new object();  
        }  
        public T Value  
        {  
            get 
            {  
                lock (m_lock)  
                    return m_value;  
            }  
            set 
            {  
                lock (m_lock)  
                    m_value = value;  
            }  
        }  
    }  

using System;
using System.Collections.Generic;
using System.Text;
namespace ReadWriteObj
{
    class MutexObj<T>
    {
        private object m_lock;
        private T m_value;
        public MutexObj(T val)
        {
            m_value = val;
            m_lock = new object();
        }
        public T Value
        {
            get
            {
                lock (m_lock)
                    return m_value;
            }
            set
            {
                lock (m_lock)
                    m_value = value;
            }
        }
    }
}

为了能够便于测试用,稍微做了点修改,把进入和退入临界区分为单独的方法,修改后代码如下:

view plaincopy to clipboardprint?
using System;  
using System.Collections.Generic;  
using System.Text;  
using System.Threading;  
namespace ReadWriteObj  
{  
    class MutexObj<T>  
    {  
        private object m_lock;  
        private T m_value;  
        public MutexObj(T val)  
        {  
            m_value = val;  
            m_lock = new object();  
        }  
        public void BeginRead()  
        {  
            Monitor.Enter(m_lock);  
        }  
        public void EndRead()  
        {  
            Monitor.Exit(m_lock);  
        }  
        public void BeginWrite()  
        {  
            Monitor.Enter(m_lock);  
        }  
        public void EndWrite()  
        {  
            Monitor.Exit(m_lock);  
        }  
    }  

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
namespace ReadWriteObj
{
    class MutexObj<T>
    {
        private object m_lock;
        private T m_value;
        public MutexObj(T val)
        {
            m_value = val;
            m_lock = new object();
        }
        public void BeginRead()
        {
            Monitor.Enter(m_lock);
        }
        public void EndRead()
        {
            Monitor.Exit(m_lock);
        }
        public void BeginWrite()
        {
            Monitor.Enter(m_lock);
        }
        public void EndWrite()
        {
            Monitor.Exit(m_lock);
        }
    }
}

这样可以很简单且稳定的实现同步,却不能完全达到我们的需求.因为无法实现多个读线程同时访问.
看样子一个锁是很难实现这样的需求,我便构思了两个锁,一个用来负责同步读操作,一个用来同步写操作,
实际上我更喜欢把锁称之为门,那么我们就有了一个读门和写门,
这样,我用两扇门来描述的需求就成了:
当读线程访问时,要先把写门关闭,然后等待读门开启,进入读门后要,保持读门继续开启,读取完毕后将写门开启,
当写线程访问时,要先把读门关闭,然后等待写们开启,进入写门后立刻关闭写门,写完之后将读门和写门都打开.
想法是好的,于是动手写了,代码如下:

view plaincopy to clipboardprint?
using System;  
using System.Collections.Generic;  
using System.Text;  
using System.Threading;  
namespace ReadWriteObj  
{  
    public class ReadWriteObj<T>  
    {  
        private T m_value;  
        private EventWaitHandle m_readLock;  
        private AutoResetEvent m_writeLock;  
        public ReadWriteObj(T val)  
        {  
            m_readLock = new EventWaitHandle(true, EventResetMode.ManualReset);  
            m_writeLock = new AutoResetEvent(true);  
            m_value = val;  
        }  
        public T Value  
        {  
            get { return Value; }  
            set { Value = value; }  
        }  
        public void BeginRead()  
        {  
            m_writeLock.Reset();  
            m_readLock.WaitOne();  
        }  
        public void EndRead()  
        {  
            m_writeLock.Set();  
        }  
        public void BeginWrite()  
        {  
            m_readLock.Reset();  
            m_writeLock.WaitOne();  
        }  
        public void EndWrite()  
        {  
            m_readLock.Set();  
            m_writeLock.Set();  
        }  
    }  

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
namespace ReadWriteObj
{
    public class ReadWriteObj<T>
    {
        private T m_value;
        private EventWaitHandle m_readLock;
        private AutoResetEvent m_writeLock;
        public ReadWriteObj(T val)
        {
            m_readLock = new EventWaitHandle(true, EventResetMode.ManualReset);
            m_writeLock = new AutoResetEvent(true);
            m_value = val;
        }
        public T Value
        {
            get { return Value; }
            set { Value = value; }
        }
        public void BeginRead()
        {
            m_writeLock.Reset();
            m_readLock.WaitOne();
        }
        public void EndRead()
        {
            m_writeLock.Set();
        }
        public void BeginWrite()
        {
            m_readLock.Reset();
            m_writeLock.WaitOne();
        }
        public void EndWrite()
        {
            m_readLock.Set();
            m_writeLock.Set();
        }
    }
}

这段代码有一个显而易见的问题,就是当读线程通过读门进去之后,读门是没有关的,
所以读线程可以正常进去,但是每一个读线程读取完毕时,都进行了m_writeLock.Set()处理,
也就是把写门开启,这时,很可能还有读线程正在读取中,而就不应该把写门开启,放写线程进入.
所以这里必须要引入一个计数器,让最后退出的一个读线程去开启写门,修改代码后如下:

view plaincopy to clipboardprint?
using System;  
using System.Collections.Generic;  
using System.Text;  
using System.Threading;  
namespace ReadWriteObj  
{  
    public class ReadWriteObj<T>  
    {  
        private T m_value;  
        private EventWaitHandle m_readLock;  
        private AutoResetEvent m_writeLock;  
        private int m_readCount = 0;  
        public ReadWriteObj(T val)  
        {  
            m_readLock = new EventWaitHandle(true, EventResetMode.ManualReset);  
            m_writeLock = new AutoResetEvent(true);  
            m_value = val;  
        }  
        public T Value  
        {  
            get { return Value; }  
            set { Value = value; }  
        }  
        public void BeginRead()  
        {  
            m_writeLock.Reset();  
            m_readLock.WaitOne();  
            ++m_readCount;  
        }  
        public void EndRead()  
        {  
            --m_readCount;  
            if(m_readCount==0)  
            {  
                m_writeLock.Set();  
            }  
        }  
        public void BeginWrite()  
        {  
            m_readLock.Reset();  
            m_writeLock.WaitOne();  
        }  
        public void EndWrite()  
        {  
            m_readLock.Set();  
            m_writeLock.Set();  
        }  
    }  

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
namespace ReadWriteObj
{
    public class ReadWriteObj<T>
    {
        private T m_value;
        private EventWaitHandle m_readLock;
        private AutoResetEvent m_writeLock;
        private int m_readCount = 0;
        public ReadWriteObj(T val)
        {
            m_readLock = new EventWaitHandle(true, EventResetMode.ManualReset);
            m_writeLock = new AutoResetEvent(true);
            m_value = val;
        }
        public T Value
        {
            get { return Value; }
            set { Value = value; }
        }
        public void BeginRead()
        {
            m_writeLock.Reset();
            m_readLock.WaitOne();
            ++m_readCount;
        }
        public void EndRead()
        {
            --m_readCount;
            if(m_readCount==0)
            {
                m_writeLock.Set();
            }
        }
        public void BeginWrite()
        {
            m_readLock.Reset();
            m_writeLock.WaitOne();
        }
        public void EndWrite()
        {
            m_readLock.Set();
            m_writeLock.Set();
        }
    }
}

自己感觉好像没有问题了,就写了个程序来测试这个对象,
结果问题很快就出现,线程之间很容易出现读和写同时发生的情况,
仔细分析原因,我认为问题出在两个地方:
1.在EndRead()中,当我执行if判断m_readCount==0成立时,这时读门是开启的,
所以很可能在执行这句之后,在执行m_writeLock.Set()之前,这时有一个读线程进入了,
但紧接着,m_writeLock.Set()语句的执行又将写门开启,
从而导致读写同时进入.
2.在EndWrite()中,此时很可能读门和写门外都等着线程,当执行了m_readLock.Set()后,还没有来得及执行m_writeLock.Set()时,
一个读线程进入了,然后m_writeLock.Set()执行,写门又被打开,写线程也进入了.

那么我就只能在读门和写门进入时,做两次关门的动作了,修改代码如下:

view plaincopy to clipboardprint?
using System;  
using System.Collections.Generic;  
using System.Text;  
using System.Threading;  
namespace ReadWriteObj  
{  
    public class ReadWriteObj<T>  
    {  
        private T m_value;  
        private EventWaitHandle m_readLock;  
        private AutoResetEvent m_writeLock;  
        private int m_readCount = 0;  
        public ReadWriteObj(T val)  
        {  
            m_readLock = new EventWaitHandle(true, EventResetMode.ManualReset);  
            m_writeLock = new AutoResetEvent(true);  
            m_value = val;  
        }  
        public T Value  
        {  
            get { return Value; }  
            set { Value = value; }  
        }  
        public void BeginRead()  
        {  
            m_writeLock.Reset();  
            m_readLock.WaitOne();  
            m_writeLock.Reset();  
            ++m_readCount;  
        }  
        public void EndRead()  
        {  
            --m_readCount;  
            if(m_readCount==0)  
            {  
                m_writeLock.Set();  
            }  
        }  
        public void BeginWrite()  
        {  
            m_readLock.Reset();  
            m_writeLock.WaitOne();  
            m_readLock.Reset();  
        }  
        public void EndWrite()  
        {  
            m_readLock.Set();  
            m_writeLock.Set();  
        }  
    }  

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
namespace ReadWriteObj
{
    public class ReadWriteObj<T>
    {
        private T m_value;
        private EventWaitHandle m_readLock;
        private AutoResetEvent m_writeLock;
        private int m_readCount = 0;
        public ReadWriteObj(T val)
        {
            m_readLock = new EventWaitHandle(true, EventResetMode.ManualReset);
            m_writeLock = new AutoResetEvent(true);
            m_value = val;
        }
        public T Value
        {
            get { return Value; }
            set { Value = value; }
        }
        public void BeginRead()
        {
            m_writeLock.Reset();
            m_readLock.WaitOne();
            m_writeLock.Reset();
            ++m_readCount;
        }
        public void EndRead()
        {
            --m_readCount;
            if(m_readCount==0)
            {
                m_writeLock.Set();
            }
        }
        public void BeginWrite()
        {
            m_readLock.Reset();
            m_writeLock.WaitOne();
            m_readLock.Reset();
        }
        public void EndWrite()
        {
            m_readLock.Set();
            m_writeLock.Set();
        }
    }
}

继续测试,大部分情况是正确,但是当EndWrite()执行后,还是有可能出现读写同时的问题,
怎么解释呢?我是这么理解的,当写完时,我是需要将读门和写门都开放,总是有读和写都同时进来的可能性,
但是我又不能加上互斥锁,因为这样造成死锁的可能性很大,看来用这样的机制是不能解决问题的,
其根源在于我要同时开启两扇门.
我就想,那么我就不用两扇门了,让门一扇一扇的开,不是就回避掉这个问题了吗?
于是我就在一个互斥锁的基础上进行了扩展,用了一个两重门的机制,注意,是两重门,而不是两扇门,
怎么解释呢?
我的设计是,不管是读还是写,要想进入临界区,就需要通过两重门,而且这两重门都是会放过一个人后就自动关闭的,
这样,大家(读和写)都在第一道门外等候,当第一道门开启后,放进一个人,那人被卡在了第二道门外等候,这时有两种情况,
1.等候的是读,他会观察临界区内的人是读还是写,如果临界区是被读占据,好,自己人,等待区的读就自己打开第二道门,
然后自己进去,门关上了,他再开第一道门,放下一个人进等候区;那如果临界区是被写占据呢?没办法,只好在等候区等待写完毕.
2.等待的是写,那无论临界区是读还是写,我总是要等待,进入临界区后,我要把第一道门开启,放下一个进等待区.
关于上面第2点,可能有人要问,如果我临界区空闲呢,那时我应该让写自己打开第二道门自己进来.不用担心,
因为我会保证在读和写离开临界区时,开启第二道门,只有一种情况例外,就是读离开临界区,临界区再没有读线程(这里计数器还是需要的),
而等待区刚好一个读正在准备进来(因为时间片的原因,这种情况是可能发生的),这时候我不需要主动开启第二道门.
照着这个思想,编写代码如下:

view plaincopy to clipboardprint?
using System;  
using System.Collections.Generic;  
using System.Text;  
using System.Threading;  
namespace ReadWriteObj  
{  
    class GetSetObj<T>  
    {  
        private AutoResetEvent m_first, m_second;  
        private T m_value;  
        private int m_readCount;  
        private Op m_waitState, m_coreState;  
        public enum Op  
        {  
            GET,  
            SET,  
            IDLE  
        };  
        public GetSetObj(T val)  
        {  
            m_value = val;  
            m_first = new AutoResetEvent(true);  
            m_second = new AutoResetEvent(true);  
            m_waitState = Op.IDLE;  
            m_coreState = Op.IDLE;  
            m_readCount = 0;  
        }  
        public Op GetCoreState()  
        {  
            return m_coreState;  
        }  
        public T Value  
        {  
            get { return m_value; }  
            set { m_value = value; }  
        }  
        public void BeginRead()  
        {  
            m_first.WaitOne();  
            m_waitState = Op.GET;  
            if (m_coreState != Op.SET)  
            {  
                m_second.Set();  
            }  
            m_second.WaitOne();  
            m_waitState = Op.IDLE;  
            m_coreState = Op.GET;  
            ++m_readCount;  
            m_first.Set();  
        }  
        public void EndRead()  
        {  
            --m_readCount;  
            if (m_readCount == 0)  
            {  
                if (m_waitState != Op.GET)  
                {  
                    if (m_waitState == Op.IDLE)  
                    {  
                        m_coreState = Op.IDLE;  
                    }  
                    m_second.Set();  
                }  
            }  
        }  
        public void BeginWrite()  
        {  
            m_first.WaitOne();  
            m_waitState = Op.SET;  
            m_second.WaitOne();  
            m_waitState = Op.IDLE;  
            m_coreState = Op.SET;  
            m_first.Set();  
              
        }  
        public void EndWrite()  
        {  
            m_second.Set();  
        }  
    }  

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
namespace ReadWriteObj
{
    class GetSetObj<T>
    {
        private AutoResetEvent m_first, m_second;
        private T m_value;
        private int m_readCount;
        private Op m_waitState, m_coreState;
        public enum Op
        {
            GET,
            SET,
            IDLE
        };
        public GetSetObj(T val)
        {
            m_value = val;
            m_first = new AutoResetEvent(true);
            m_second = new AutoResetEvent(true);
            m_waitState = Op.IDLE;
            m_coreState = Op.IDLE;
            m_readCount = 0;
        }
        public Op GetCoreState()
        {
            return m_coreState;
        }
        public T Value
        {
            get { return m_value; }
            set { m_value = value; }
        }
        public void BeginRead()
        {
            m_first.WaitOne();
            m_waitState = Op.GET;
            if (m_coreState != Op.SET)
            {
                m_second.Set();
            }
            m_second.WaitOne();
            m_waitState = Op.IDLE;
            m_coreState = Op.GET;
            ++m_readCount;
            m_first.Set();
        }
        public void EndRead()
        {
            --m_readCount;
            if (m_readCount == 0)
            {
                if (m_waitState != Op.GET)
                {
                    if (m_waitState == Op.IDLE)
                    {
                        m_coreState = Op.IDLE;
                    }
                    m_second.Set();
                }
            }
        }
        public void BeginWrite()
        {
            m_first.WaitOne();
            m_waitState = Op.SET;
            m_second.WaitOne();
            m_waitState = Op.IDLE;
            m_coreState = Op.SET;
            m_first.Set();
           
        }
        public void EndWrite()
        {
            m_second.Set();
        }
    }
}

好,测试程序跑起来,也没有发现问题,看来似乎是符合要求了.
附上测试程序的代码,供参考

 

view plaincopy to clipboardprint?
using System;  
using System.Collections.Generic;  
using System.ComponentModel;  
using System.Data;  
using System.Drawing;  
using System.Text;  
using System.Windows.Forms;  
namespace ReadWriteObj  
{  
    public partial class Form1 : Form  
    {  
        public Form1()  
        {  
            InitializeComponent();  
        }  
        //private ReadWriteObj<string> m_obj = new ReadWriteObj<string>("");  
        private GetSetObj<string> m_obj = new GetSetObj<string>("");  
        private delegate void SetStr(Control con, string msg,Color colr);  
        private void SetMsg(Control con, string m, Color c)  
        {  
            if (this.InvokeRequired)  
            {  
                SetStr d = new SetStr(SetMsg);  
                this.Invoke(d, con, m, c);  
            }  
            else 
            {  
                con.Text = m;  
                con.BackColor = c;  
            }  
        }  
        private void Form1_Load(object sender, EventArgs e)  
        {  
            for (int i = 1; i <= 12; ++i)  
            {  
                System.Threading.ThreadPool.QueueUserWorkItem(new System.Threading.WaitCallback(ThreadProc), this.Controls["label" + i.ToString()]);  
            }  
        }  
        void ThreadProc(object o)  
        {  
            Control c = o as Control;  
            Random r = new Random();  
            while (true)  
            {  
                int temp = r.Next(1, 100);  
                string str = "";  
                if (temp <= 50)  
                {  
                    str = c.Name + " Read";  
                    SetMsg(c, str, Color.Yellow);  
                    m_obj.BeginRead();  
                    SetMsg(c, str, Color.Red);  
                    SetMsg(labelObj, str, Color.Red);  
                    temp = r.Next(1, 2) * 1000;  
                    System.Threading.Thread.Sleep(temp);  
                    SetMsg(labelObj, str, Color.Yellow);  
                    m_obj.EndRead();  
                }  
                else 
                {  
                    str = c.Name + " Write";  
                    SetMsg(c, str, Color.Yellow);  
                    m_obj.BeginWrite();  
                    SetMsg(c, str, Color.White);  
                    SetMsg(labelObj, str, Color.White);  
                    temp = r.Next(1, 2) * 1000;  
                    System.Threading.Thread.Sleep(temp);  
                    SetMsg(labelObj, str, Color.Yellow);  
                    m_obj.EndWrite();  
                }  
                str = c.Name + " Sleep";  
                SetMsg(c, str, Color.Yellow);  
                temp = r.Next(6, 8) * 1000;  
                System.Threading.Thread.Sleep(temp);  
            }  
        }  
    }  

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
namespace ReadWriteObj
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
        //private ReadWriteObj<string> m_obj = new ReadWriteObj<string>("");
        private GetSetObj<string> m_obj = new GetSetObj<string>("");
        private delegate void SetStr(Control con, string msg,Color colr);
        private void SetMsg(Control con, string m, Color c)
        {
            if (this.InvokeRequired)
            {
                SetStr d = new SetStr(SetMsg);
                this.Invoke(d, con, m, c);
            }
            else
            {
                con.Text = m;
                con.BackColor = c;
            }
        }
        private void Form1_Load(object sender, EventArgs e)
        {
            for (int i = 1; i <= 12; ++i)
            {
                System.Threading.ThreadPool.QueueUserWorkItem(new System.Threading.WaitCallback(ThreadProc), this.Controls["label" + i.ToString()]);
            }
        }
        void ThreadProc(object o)
        {
            Control c = o as Control;
            Random r = new Random();
            while (true)
            {
                int temp = r.Next(1, 100);
                string str = "";
                if (temp <= 50)
                {
                    str = c.Name + " Read";
                    SetMsg(c, str, Color.Yellow);
                    m_obj.BeginRead();
                    SetMsg(c, str, Color.Red);
                    SetMsg(labelObj, str, Color.Red);
                    temp = r.Next(1, 2) * 1000;
                    System.Threading.Thread.Sleep(temp);
                    SetMsg(labelObj, str, Color.Yellow);
                    m_obj.EndRead();
                }
                else
                {
                    str = c.Name + " Write";
                    SetMsg(c, str, Color.Yellow);
                    m_obj.BeginWrite();
                    SetMsg(c, str, Color.White);
                    SetMsg(labelObj, str, Color.White);
                    temp = r.Next(1, 2) * 1000;
                    System.Threading.Thread.Sleep(temp);
                    SetMsg(labelObj, str, Color.Yellow);
                    m_obj.EndWrite();
                }
                str = c.Name + " Sleep";
                SetMsg(c, str, Color.Yellow);
                temp = r.Next(6, 8) * 1000;
                System.Threading.Thread.Sleep(temp);
            }
        }
    }
}

 

抱歉!评论已关闭.