zoukankan      html  css  js  c++  java
  • MethodImpl(MethodImplOptions.Synchronized)]、lock(this)与lock(typeof(...)) (转)

    对于稍微有点经验的.NET开发人员来说,倘若被问及如何保持线程同步,我想很多人都能说好好几种。在众多的线程同步的可选方式中,加锁无疑是最为常用的。如果仅仅是基于方法级别的线程同步,使用System.Runtime.CompilerServices.MethodImplAttribute无疑是最为简洁的一种方式。MethodImplAttribute可以用于instance method,也可以用于static method。当在某个方法上标注了MethodImplAttribute,并指定MethodImplOptions.Synchronized参数,可以确保在不同线程中运行的该方式以同步的方式运行。我们几天来讨论MethodImplAttribute(MethodImplOptions.Synchronized)和lock的关系。

    一、提出结论

    在进行讨论之前,我先提出下面3个结论:

    1、[MethodImplAttribute(MethodImplOptions.Synchronized)]仍然采用加锁的机制实现线程的同步。

    2、如果[MethodImplAttribute(MethodImplOptions.Synchronized)]被应用到instance method,相当于对当前实例加锁。

    3、如果[MethodImplAttribute(MethodImplOptions.Synchronized)]被应用到static method,相当于当前类型加锁

    二、基于instance method的线程同步

    为了验证我们上面提出的结论,我作了一个小小的例子。在一个console application中定义了一个class:SyncHelper,其中定义了一个方法Execute。打印出方法执行的时间,并休眠当前线程模拟一个耗时的操作:

    class SyncHelper
    {
        public void Execute()
        {
            Console.WriteLine("Excute at {0}", DateTime.Now);
            Thread.Sleep(5000);
        }
    }

    在入口Main方法中,创建SyncHelper对象,通过一个System.Threading.Timer对象实现每隔1s调用该对象的Execute方法:

    class Program
    {
        static void Main(string[] args)
        {
            SyncHelper helper = new SyncHelper();
            Timer timer = new Timer(
            delegate
            {
                helper.Execute();
            }, null, 0, 1000);

            Console.Read();

        }
    }

    由于Timer对象采用异步的方式进行调用,所以虽然Execute方法的执行时间是5s,但是该方法仍然是每隔1s被执行一次。这一点从最终执行的结果可以看出:

    image

    为了让同一个SyncHelper对象的Execute方法同步执行,我们在Execute方法上添加了如下一个MethodImplAttribute:

    [MethodImpl(MethodImplOptions.Synchronized)]
    public void Execute()
    {
        Console.WriteLine("Excute at {0}", DateTime.Now);
        Thread.Sleep(5000);
    }

    从如下的输出结果我们可以看出Execute方法是以同步的方式执行的,因为两次执行的间隔正式Execute方法执行的时间:

    image

    在一开始我们提出的结论中,我们提到“如果[MethodImplAttribute(MethodImplOptions.Synchronized)]被应用到instance method,相当于对当前实例加锁”。说得直白一点:[MethodImplAttribute(MethodImplOptions.Synchronized)] = lock(this)。我们可以通过下面的实验验证这一点。为此,在SyncHelper中定义了一个方法LockMyself。在此方法中对自身加锁,并持续5s中,并答应加锁和解锁的时间。

    public void LockMyself()
    {
       lock (this)
        {
            Console.WriteLine("Lock myself at {0}", DateTime.Now);
            Thread.Sleep(5000);
            Console.WriteLine("Unlock myself at {0}", DateTime.Now);
        }
    }

    我们在Main()中以异步的方式(通过创建新的线程的方式)调用该方法:

    static void Main(string[] args)
    {
        SyncHelper helper = new SyncHelper();

        Thread thread = new Thread(
            delegate()
            {           

                 helper.LockMyself();

            });
        thread.Start();
        Timer timer = new Timer(
        delegate
        {
            helper.Execute();
        }, null, 0, 1000);

        Console.Read();
    }

    结合我们的第二个结论想想最终的输出会是如何。由于LockMyself方法是在另一个线程中执行,我们可以简单讲该方法的执行和Execute的第一个次执行看作是同时的。但是MethodImplAttribute(MethodImplOptions.Synchronized)]果真是通过lock(this)的方式实现的话,Execute必须在等待LockMyself方法执行结束将对自身的锁释放后才能得以执行。也就是说LockMyself和第一次Execute方法的执行应该相差5s。而输出的结果证实了这点:

    image

    三、基于static method的线程同步

    讨论完再instance method上添加MethodImplAttribute(MethodImplOptions.Synchronized)]的情况,我们相同的方式来讨论倘若一样的MethodImplAttribute被应用到static方法,又会使怎样的结果。

    我们先将Execute方法上的MethodImplAttribute注释掉,并将其改为static方法:

    //[MethodImpl(MethodImplOptions.Synchronized)]
    public static void Execute()
    {
        Console.WriteLine("Excute at {0}", DateTime.Now);
        Thread.Sleep(5000);
    }

    在Main方法中,通过Timer调用该static方法:

    static void Main(string[] args)
    {
        Timer timer = new Timer(
        delegate
        {
            SyncHelper.Execute();
        }, null, 0, 1000);

        Console.Read();
    }

    毫无疑问,Execute方法将以1s的间隔异步地执行,最终的输出结果如下:

    image

    然后我们将对[MethodImpl(MethodImplOptions.Synchronized)]的注释取消:

    [MethodImpl(MethodImplOptions.Synchronized)]
    public static void Execute()
    {
        Console.WriteLine("Excute at {0}", DateTime.Now);
        Thread.Sleep(5000);
    }

    最终的输出结果证实了Execute将会按照我们期望的那样以同步的方式执行,执行的间隔正是方法执行的时间:

    image

    我们回顾一下第三个结论:“如果[MethodImplAttribute(MethodImplOptions.Synchronized)]被应用到static method,相当于当前类型加锁”。为了验证这个结论,在SyncHelper中添加了一个新的static方法:LockType。该方法对SyncHelper tpye加锁,并持续5s中,在加锁和解锁是打印出当前时间:

    public static void LockType()
    {
        lock (typeof(SyncHelper))
        {
            Console.WriteLine("Lock SyncHelper type at {0}", DateTime.Now);
            Thread.Sleep(5000);
            Console.WriteLine("Unlock SyncHelper type at {0}", DateTime.Now);
        }
    }

    在Main中,像验证instance method一样,创建新的线程执行LockType方法:

    static void Main(string[] args)
    {
        Thread thread = new Thread(
            delegate()
            {
                SyncHelper.LockType();
            });
        thread.Start();

        Timer timer = new Timer(
        delegate
        {
            SyncHelper.Execute();
        }, null, 0, 1000);

        Console.Read();
    }

    如果基于static method的[MethodImplAttribute(MethodImplOptions.Synchronized)]是通过对Type进行加锁实现。那么通过Timer轮询的第一个Execute方法需要在LockType方法执行完成将对SyncHelper type的锁释放后才能执行。所以如果上述的结论成立,将会有下面的输出:

    image

    四、总结

    对于加锁来说,锁的粒度的选择显得至关重要。在不同的场景中需要选择不同粒度的锁。如果选择错误往往会对性能造成很到的影响,严重时还会引起死锁。就拿[MethodImplAttribute(MethodImplOptions.Synchronized)]来说,如果开发人员对它的实现机制不了解,很有可能使它lock(this)或者lock(typeof(…))并存,造成方法得不到及时地执行。

    最后说一句题外话,因为字符串驻留机制的存在,切忌对string进行加锁。

  • 相关阅读:
    POJ2778 DNA Sequence AC自动机上dp
    codeforces732F Tourist Reform 边双联通分量
    codeforces786B Legacy 线段树优化建图
    洛谷P3588 PUS 线段树优化建图
    codeforces1301D Time to Run 模拟
    codeforces1303B National Project 二分或直接计算
    codeforces1303C Perfect Keyboard 模拟或判断欧拉路
    codeforces1303D Fill The Bag 二进制应用+贪心
    python之路——使用python操作mysql数据库
    python之路——mysql索引原理
  • 原文地址:https://www.cnblogs.com/aaa6818162/p/1535996.html
Copyright © 2011-2022 走看看