.Net下的线程同步:Part 4 of N--(Locks or Critical sections)

Locks(或者Critical sections)

        锁定是一种一次只允许一个线程进入特定代码区段的机制,通过加锁实现。被锁定的代码区段称为critical section(关键区域)。锁定一段代码的方式有多种,下面将一一介绍。在介绍前,我们先来看看什么情况需要锁定:

using System;
using System.Threading;

namespace NoLockTest
{
    /// <summary>
    /// This program is not thread safe
    /// as it could be accessed by 2 different 
    /// simultaneous threads. As one thread could 
    /// be set item2 to 0, just at the point that 
    /// another thread was doing the division, 
    /// leading to a DivideByZeroException
    /// </summary>
    class Program
    {
        static int item1=54, item2=21;
        public static Thread T1;
        public static Thread T2;

        static void Main(string[] args)
        {
            T1 = new Thread((ThreadStart)delegate
                {
                    DoCalc();
                });
            T2 = new Thread((ThreadStart)delegate
            {
                DoCalc();
            });
            T1.Name = "T1";
            T2.Name = "T2";
            T1.Start();
            T2.Start();
            Console.ReadLine();
        }

        private static void DoCalc()
        {
              item2 = 10;
            if (item1 != 0)
                Console.WriteLine(item1 / item2);
            item2 = 0;
        }
    }
}

       上面的代码不是线程安全的,因为它可能同时被两个线程操作,一个线程可能将item2 置为零,而此时如果另一线程做除法,则会导致DivideByZeroException。尽管这种问题出现的几率比较小,难于调试发现,但这正是多线程问题复杂所在。因此,为避免这种问题的出现,我们需要采取安全措施。我们可以通过lock来解决这个问题:

using System;
using System.Threading;

namespace LockTest
{
    /// <summary>
    /// This shows how to create a critical section
    /// using the lock keyword
    /// </summary>
    class Program
    {
        static object syncLock = new object();
        static int item1 = 54, item2 = 21;
        public static Thread T1;
        public static Thread T2;

        static void Main(string[] args)
        {
            T1 = new Thread((ThreadStart)delegate
            {
                DoCalc();
            });
            T2 = new Thread((ThreadStart)delegate
            {
                DoCalc();
            });
            T1.Name = "T1";
            T2.Name = "T2";
            T1.Start();
            T2.Start();
            Console.ReadLine();
        }

        private static void DoCalc()
        {
            lock (syncLock)
            {
                item2 = 10;
                if (item1 != 0)
                    Console.WriteLine(item1 / item2);
                item2 = 0;
            }
        }
    }
}

        在这个例子中,lock关键字所包含的区域即为关键区域(critical sections)。每次只有一个线程可以锁定同步对象(syncLock)。任何其它的竞争线程都将被阻塞,直到syncLock被释放。等待线程处于一种队列状态,先道者可以优先得到服务。

        有些读者可能会用lock(this) 或者lock(typeof(MyClass))来作为同步对象,这是一个坏习惯,因为这些变量都是公开的,所以外部实体也有可能用它们来同步,从而对你的线程造成干扰,因此最好使用私有的同步对象。

        下面简要介绍一下加锁的几种方式:

Lock 关键字

         lock关键字的例子在上面已经出现,我们可以用它来锁定某个对象。其实lock是Monitor 类的简写方式,下面将会提到。

Monitor 类

         Monitor类可以达到lock关键字同样的效果。示例如下:

using System;
using System.Threading;

namespace LockTest
{
    /// <summary>
    /// This shows how to create a critical section
    /// using the Monitor class
    /// </summary>
    class Program
    {
        static object syncLock = new object();
        static int item1 = 54, item2 = 21;


        static void Main(string[] args)
        {
            Monitor.Enter(syncLock);
            try
            {
                if (item1 != 0)
                    Console.WriteLine(item1 / item2);
                item2 = 0;
            }
            finally
            {
                Monitor.Exit(syncLock);
            }
            Console.ReadLine();
        }
    }
}

         可以看出lock其实只是下面代码的语法糖而已:

Monitor.Enter(syncLock);
try
{

}
finally
{
    Monitor.Exit(syncLock);
}

MethodImpl.Synchronized 属性

         通过使用该属性,使得一个函数本身被同步。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MethodImplSynchronizedTest
{
    /// <summary>
    /// This shows how to create a critical section
    /// using the System.Runtime.CompilerServices.MethodImplAttribute
    /// </summary>
    class Program
    {
        static int item1=54, item2=21;

        static void Main(string[] args)
        {
            //make a call to different method
            //as cant Synchronize Main method
            DoWork();
        }

        [System.Runtime.CompilerServices.MethodImpl
        (System.Runtime.CompilerServices.MethodImplOptions.Synchronized)]
        private static void DoWork()
        {
            if (item1 != 0)
                Console.WriteLine(item1 / item2);
            item2 = 0;
            Console.ReadLine();
        }
    }
}

        上述例子说明了如何通过 System.Runtime.CompilerServices.MethodImplAttribute来使一个函数同步(critical section)。

        有一点要注意的是,为了避免性能损失,锁定的区域要尽可能的小,尽管你可以锁定整个函数,但是如非必要,只锁定有同步需要的变量即可。


你可能感兴趣的:(thread,多线程,.net,object,String,Class)