C#中的lock关键字有何作用

  作为C#的程序员来说,在遇到线程同步的需求时最常用的就是lock关键字。但如何正确并有效地使用lock,却是能否高效地达到同步要求的关键。正因为如此,程序员需要完全理解lock究竟为程序做了什么。

  所涉及的知识点

• lock的等效代码

• System.Threading.Monitor类型的作用和使用方法

  分析问题

1.lock的等效代码

  在.NET的多线程程序中,经常会遇到lock关键字来控制同步,比如下列代码:

private object o = new object();

public void Work()

{

  lock(o)

  {

    //做一些需要线程同步的工作

  }

}

  事实上,lock这个关键字是C#为方便程序员而定义的语法,它等效于安全地使用System.Threading.Monitor类型。上面的代码就直接等效于下面的代码:

private object o = new object();

public void Work()

{

  //这里很重要,是为了避免直接使用私有成员o,而导致线程不安全

  object temp = o;

  System.Threading.Monitor.Enter(temp);

  try

  {

    //做一些需要线程同步的工作

  }

  finally

  {

    System.Threading.Monitor.Exit(temp);

  }

}

  正如你看到的,真正实现了线程同步功能的,就是System.Threading.Monitor类型,lock关键字只是用来代替调用Enter、Exit方法,并且将所有的工作包含在try块内,以保证其最终退出同步。

2.System.Threading.Monitor类型的作用和使用

  在前文中已经提到了,Monitor类型的Enter和Exit方法用来实现进入和退出对象的同步。具体来说,当Enter方法被调用时,对象的同步索引将被检查,并且.NET将负责一系列的后续工作,来保证对象访问时线程的同步,而Exit方法的调用,则保证了当前线程释放该对象的同步块。

  示例

  下列演示了自定义的类如何利用lock关键字(也就是Monitor类型)来实现线程同步,具体定义了一个包含需要同步执行方法的类型。

/// <summary>

/// 演示同步锁

/// </summary>

public class MyLock

{

    //用来在静态方法中同步

    private static object o1 = new object();

    //用来在成员方法中不同

    private object o2 = new object();

    //成员变量

    private static int i1 = 0;

    private int i2 = 0;

    /// <summary>

    /// 测试静态方法的同步

    /// </summary>

    /// <param name=" handleObject ">同步时操作的对象</param>

    public static void Increment1(object handleObject)

    {

        lock (o1)

        {

            Console.WriteLine("i1的值为:{0}", i1);

            //这里刻意制造线程并行机会,来检查同步的功能

            Thread.Sleep(200);

            i1++;

            Console.WriteLine("i1自增后为:{0}", i1);

        }

    }

    /// <summary>

    /// 测试成员方法的同步

    /// </summary>

    /// <param name=" handleObject ">同步时操作的对象</param>

    public void Increment2(object handleObject)

    {

        lock (o2)

        {

            Console.WriteLine("i2的值为:{0}", i2);

            //这里刻意制造线程并行机会,来检查同步的功能

            Thread.Sleep(200);

            i2++;

            Console.WriteLine("i2自增后为:{0}", i2);

        }

    }

}

这样在main方法中,调用该类型对象的方法和其静态方法,测试其同步的效果。代码如下

/// <summary>

/// 程序入口

/// </summary>

class Program

{

  /// <summary>

   /// 测试同步效果

   /// </summary>

  static void Main(string[] args)

  {

         //开始多线程

         Console.WriteLine("开始测试静态方法的同步");

         for (int i = 0; i < 5; i++)

         {

             Task t = new Task(MyLock.Increment1, i);

             t.Start();

         }

         //这里等待线程执行结束

        Thread.Sleep(3 * 1000);

         Console.WriteLine("开始测试成员方法的同步");

         MyLock myLock = new MyLock();

         //开始多线程

         for (int i = 0; i < 5; i++)

        {

             Thread t = new Thread(myLock.Increment2);

             t.Start();

         }

         Console.Read();

  }

}

下面是程序的执行结果:

开始测试静态方法的同步

i1的值为:0

i1自增后为:1

i1的值为:1

i1自增后为:2

i1的值为:2

i1自增后为:3

i1的值为:3

i1自增后为:4

i1的值为:4

i1自增后为:5

开始测试成员方法的同步

i2的值为:0

i2自增后为:1

i2的值为:1

i2自增后为:2

i2的值为:2

i2自增后为:3

i2的值为:3

i2自增后为:4

i2的值为:4

i2自增后为:5

   总结

  可以看到,线程同步被很好地保证了。这里需要强调的是,线程同步本身违反了多线程并行运行的原则,所以读者在使用线程同步时应该尽量做到把lock加在最小的程序块上。如果一个方法有大量的代码需要线程同步,那就需要重新考虑程序的设计了,是否真的有必要进行多线程处理,毕竟线程本身的开销也是相当大的。

  对静态方法的同步,一般采用静态私有的引用成员,而对成员方法的同步,一般采用私有的引用成员。读者需要注意静态和非静态成员的使用,都把同步对象申明为私有,这是保证线程同步高效并且正确的关键点。

说明:以上内容是根据网上内容进行整理。

原文地址:https://www.cnblogs.com/zwt-blog/p/4812627.html