C#学习随笔,同步方法总结

说在最前面

在多线程中编程中,少不了需要对共享资源的同步。此时就需要对资源同步的相关知识,在C#中,提供了如下几种线程资源同步的方式。

将字段使用volatile关键字修饰

当一个字段被声明为volatile关键字时,CLR中一些管理代码和内存的内部机制将负责对字段进行同步,并且总能保证读取到的字段信息都为最新的值,
声明为volatile的关键字必须具备如下:

  1. 引用类型
  2. (不安全代码的)指针
  3. sbyte,byte,short,ushort,int,uint,char,float,bool
  4. 一个使用底层类型的枚举类型。
class TestVolatile
{
	public volatile int Number;
}

System.Threading.Interlocked 类

在多线程中对整数自增操作,我们都容易忽略其操作的原子性。如

int i = 0;
i += 1;

i += i 可以分为三步

  1. 从内存中读取i的值
  2. 将读取出来的值加一
  3. 新值写入内存

在多线程中上述任何一步操作都有可能被打断,从而导致实际的值并不是我们所需要的,此时就需要保证操作的原子性。
.NET框架提供Interlock类来提供原子性,其中有三个方法。

Increment		//自增
Decrement		//自减
Exchange        //交换
int i = 0;
System.Threading.Interlocked.Increment(ref i);
Console.WriteLine(i);            System.Threading.Interlocked.Decrement(ref i);
Console.WriteLine(i);
System.Threading.Interlocked.Exchange(ref i, 100);
Console.WriteLine(i);

lock关键字

lock关键字获得一个对象的侵占权,提供了简单资源的同步方式,.NET中不提倡lock(this)
简单使用

Object o = new Object
lock(0)
{
.....
}

System.Theading.Monitor类

该类提供了与lock关键字类似的功能,而与lock不同的是,能够更好的控制同步块,该类Enter(object o)方法调用后,会获取o的侵占权。直到调用Exit(object o)方法时才会释放。且Enter与Exit是配套的,并且还该类提供了TryEnter方法来尝试侵占

class Program
    {
        private static object m_monitorObject = new object();
        static void Main(string[] args)
        {
            Thread thread = new Thread(Do);
            thread.Name = "Thread1";
            Thread thread2 = new Thread(Do);
            thread2.Name = "Thread2";
            thread.Start();
            thread2.Start();
            thread.Join();
            thread2.Join();

        }

        static void Do()
        {
            if (!Monitor.TryEnter(m_monitorObject))
            {
                Console.WriteLine("Can't visit Object " + Thread.CurrentThread.Name);
                return;
            }
            try
            {
                Monitor.Enter(m_monitorObject);
                Console.WriteLine("Enter Monitor " + Thread.CurrentThread.Name);
                Thread.Sleep(5000);
            }
            finally
            {
                Monitor.Exit(m_monitorObject);
            }
        }

在这里插入图片描述
线程1获取了锁,线程2尝试获取报出错误。

Monitor类更高级的功能

当你走到餐厅时候,发现没有位子,于是你开始等待。当餐厅有空位子时,通知服务生给你安排了一个位置,于是你开始就餐

Monitor提供了三个静态方法:

Monitor.Pulse(Object o)
Monitor.PulseAll(Object o)
Monitor.Wait(Object o ) // 重载函数

可以解决上述问题。
当调用Wait方法时,线程释放资源的独占锁,并且阻塞在Wait方法,直到另外的线程获取资源的独占锁后,更新资源信息并调用Pulse方法后返回。


        private static object minotorObject = new object();
        static void Main(string[] args)
        {

            Thread t1 = new Thread(new ThreadStart(HaveLunch));
            Thread t2 = new Thread(new ThreadStart(AdvanceFreeChange));

            t1.Start();
            Console.WriteLine("Wait for free seats");
            Thread.Sleep(2000);

            t2.Start();

            Console.Read();


          
        }

        public static void HaveLunch()
        {
            lock(minotorObject)
            {
                Console.WriteLine("Customer Wait free seats");
                Monitor.Wait(minotorObject);
                Console.WriteLine("Oh I am eating...");
            }
        }

        public static void AdvanceFreeChange()
        {
            lock(minotorObject)
            {
                Console.WriteLine("This have free seats");

                Monitor.Pulse(minotorObject);
            }
        }

执行结果,当使用Wait方法后,HaveLunch方法阻塞在调用地方,直到Pluse调用后,返回。
C#学习随笔,同步方法总结_第1张图片

System.Threading.Mutex(互斥体)类实现同步

Mutex虽然大多数与Minotor相同,但是其不具备Wait Pluse PluseAll方法,即不具备通知响应的功能,并且Mutex是跨进程的

private static Mutex mutex = new Mutex();

        static void Main(string[] args)
        {
            for(int i = 0; i < 3; i++)
            {
                Thread thread = new Thread(new ThreadStart(ThreadPro));
                thread.Name = String.Format("Thread{0}", i + 1);
                thread.Start();

            }

            
        }

        public static void ThreadPro()
        {
            for(int i = 0; i < 5; i++)
            {
                UserResource();
            }
        }

        private static void UserResource()
        {
            try
            {
                mutex.WaitOne();

                Console.WriteLine("{0} has entered the protected area", Thread.CurrentThread.Name);

                Thread.Sleep(500);

                Console.WriteLine("{0} is leaving the protected area\r\n",Thread.CurrentThread.Name);
            }
            finally
            {
                mutex.ReleaseMutex();
            }
         
        }

运行结果
C#学习随笔,同步方法总结_第2张图片

  • WaitOne() / WaitOne(Int32, Boolean) / WaitOne(TimeSpan, Boolean):请求所有权,该调用会一直阻塞到当前 mutex 收到信号,或直至达到可选的超时间隔。这几个方法除了不需要提供锁定对象作为参数外,看起来与Monitor上的Wait()方法及其重载很相似相似。不过千万不要误会,WaitOne()本质上跟Monitor.Enter()/TryEnter()等效,而不是Monitor.Wait()!这是因为这个WaitOne()并没有办法在获取控制权以后象Monitor.Wait()释放当前Mutex,然后阻塞自己。
  • ReleaseMutex():释放当前 Mutex 一次。注意,这里强调了一次,因为拥有互斥体的线程可以在重复的调用Wait系列函数而不会阻止其执行;这个跟Monitor的Enter()/Exit()可以在获取对象锁后可以被重复调用一样。Mutex被调用的次数由公共语言运行库(CLR)保存,每WaitOne()一次计数+1,每ReleaseMutex()一次计数-1,只要这个计数不为0,其它Mutex的等待者就会认为这个Mutex没有被释放,也就没有办法获得该Mutex。 另外,跟Monitor.Exit()一样,只有Mutex的拥有者才能RleaseMutex(),否则会引发异常。

你可能感兴趣的:(C#)