并发

一个lock锁就可以分出低中高水平的程序员对问题的处置方式

说到lock锁,我相信在座的各位没有不会用的,而且还知道怎么用不会出错,但让他们聊一聊为什么可以锁住,都说人以群分,大概就有了下面低中高水平的三类人吧。第一类人将lock对象定义成static,这样就能让多个线程看到同一个对象,以此实现线程间互斥和保证同步,如果再深问为什么?就怕遮遮掩掩的说好像每个实例都有一个同步块索引,再展开的话就顶不住了,反正大家都这么写,我也不敢问,我也不会说,如果上代码,

说到lock锁,我相信在座的各位没有不会用的,而且还知道怎么用不会出错,但让他们聊一聊为什么可以锁住,都说人以群分,大概就有了下面低中高水平的三类人吧。

第一类人

将lock对象定义成static,这样就能让多个线程看到同一个对象,以此实现线程间互斥和保证同步,如果再深问为什么?就怕遮遮掩掩的说好像每个实例都有一个同步块索引,再展开的话就顶不住了,反正大家都这么写,我也不敢问,我也不会说,如果上代码,只能这样丢给你。

    public class Program
    {
        public static object lockMe = new object();

        public static void Main(string[] args)
        {
            var task1 = Task.Factory.StartNew(() =>
            {
                lock (lockMe)
                {
                    //todo
                }
            });

            var task2 = Task.Factory.StartNew(() =>
            {
                lock (lockMe)
                {
                    //todo
                }
            });

            Task.WaitAll(task1, task2);
        }
    }复制代码

第二类人

这类人可能看过CLR via C# 这样类似圣经级著作,而且对相关概念也比较清楚。

1. 清楚‘引用类型’ 在堆上的布局结构及栈上的指针是指向方法表索引(类型对象指针),如下图。

76e364612e70ea9912e9d2dbe3112350.png

2. 清楚当lock住对象后,它的‘同步块索引’ 和 CLR上的‘同步块数组’是呈现一个关联关系,然后又是一张图。

牛X点: 仅仅用了两张图就把这个事情解决的相当完美,读者一看就明白了,然来是每个线程在lock的时候会查看一下对象的同步块索引所映射的同步块数组中的坑中信息来判断是否可以加锁。

不足点: 一定要挑刺的话,那就是这类人只是在听别人讲故事,到底是不是真的如此其实自己心里也没谱,只是一味的相信对方的人格魅力,而真正的人,十句话中只有一句假话~

第三类人

这类人就会动用资源或者人脉亲自尝试一下是不是如第二类人所描述的那样,操刀的话,最好的工具就是windbg,接下来我就操刀一把。

1. 对‘引用类型’布局结构的补充

现在大家也知道了每个对象都有两个额外开销,就是‘同步块索引’ + '方法表索引',在x86系统中,每个索引各占4字节,而在x64系统中,每个索引各占8字节,因我的系统是x64,按照x64版本测试。

2. 案例代码

有了上面的知识补充,接下来我开两个task,在task中进行lock操作。

namespace ConsoleApp2
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var employee = new Employee();

            Console.WriteLine("步骤一:lock前!!!");
            Console.ReadLine();

            var task1 = Task.Factory.StartNew(() =>
            {
                lock (employee)
                {
                    Console.WriteLine("步骤二:lock1中。。。。");
                    Console.ReadLine();
                }
                Console.WriteLine("步骤二:退出lock1...");
            });

            var task2 = Task.Factory.StartNew(() =>
            {
                lock (employee)
                {
                    Console.WriteLine("步骤二:lock2中。。。。");
                    Console.ReadLine();
                }
                Console.WriteLine("步骤二:退出lock2...");
            });

            Task.WaitAll(task1, task2);
            Console.WriteLine("步骤三: lock后,全部退出!");
            Console.ReadLine();
        }
    }

    public class Employee
    {
        public int a = 1;
        public int b = 2;
    }
}复制代码

3. 使用windbg调试

我准备分三步骤实现,lock前,lock中,lock后,然后拿到这三种情况下的dump文件来展示 employee 对象的同步块索引 和 CLR全局同步块数组实时情况。

<1> lock前

先把程序跑起来,再从任务管理器中生成dump文件。

!threads -> ~0s -> !clrstack -l 这三个命令是为了寻找主线程栈上的局部变量 employee 的内存地址。

0:000> !threads
ThreadCount:      2
UnstartedThread:  0
BackgroundThread: 1
PendingThread:    0
DeadThread:       0
Hosted Runtime:   no
                                                                                                        Lock  
       ID OSID ThreadOBJ           State GC Mode     GC Alloc Context                  Domain           Count Apt Exception
   0    1 40b8 0

原创不易,完成人机校验,阅读全文

相关推荐