zoukankan      html  css  js  c++  java
  • 《C#多线程编程实战》1.10 lock关键字

    lock关键字是锁定资源用的。

    书上的代码解释很好。

       /// <summary>
        /// 抽象类 加减法
        /// </summary>
         abstract class CounterBase
        {
            /// <summary>
            ///抽象 加法 方法
            /// </summary>
            public abstract void Increment();
    
            /// <summary>
            /// 抽象 减法 方法
            /// </summary>
            public abstract void Decrement();
        }
        /// <summary>
        /// 不使用lock关键字的实现抽象类Counter
        /// </summary>
        class Counter : CounterBase
        {
            public int Count { get; private set; }
            /// <summary>
            /// 非Lock关键字减法
            /// </summary>
            public override void Decrement()
            {
                Count--;
            }
            /// <summary>
            /// 非lock关键字加法
            /// </summary>
            public override void Increment()
            {
                Count++;
            }
        }
        /// <summary>
        /// 使用Lock关键字的类
        /// </summary>
        class CounterWidthLock : CounterBase
        {
            /// <summary>
            /// 判断是否锁定资源
            /// </summary>
            public readonly object _syncRoot = new object();
    
            public int Count { get; private set; }
    
            /// <summary>
            /// Lock关键字减法
            /// </summary>
            public override void Decrement()
            {
                lock (_syncRoot)//lock关键字,锁定资源
                {
                    Count--;
                }
            }
            /// <summary>
            /// lock关键字加法
            /// </summary>
            public override void Increment()
            {
                lock (_syncRoot)//lock关键字,锁定资源
                {
                    Count++;
                }
            }
        }
        /// <summary>
        /// 测试主类
        /// </summary>
        class Program
        {
          /// <summary>
          /// 测试主程序入口
          /// </summary>
          /// <param name="args"></param>
            static void Main(string[] args)
            {
                Console.WriteLine("不使用Lock关键字 Counter类");
    
                var NotLockClass = new Counter();
    
                var t1 = new Thread(() =>TestCounter(NotLockClass));
    
                var t2 = new Thread(() => TestCounter(NotLockClass));
    
                var t3= new Thread(() => TestCounter(NotLockClass));
    
                t1.Start();
    
                t2.Start();
    
                t3.Start();
    
                t1.Join();
    
                t2.Join();
    
                t3.Join();
    
                Console.WriteLine($"最终输出结果是{NotLockClass.Count}");
    
                Console.ReadKey();
            }
            static void TestCounter(CounterBase counterBase)
            {
                for(int i=0;i<10000;i++)
                {
                    counterBase.Increment();//加法
    
                    counterBase.Decrement();//减法
                }
            }
        }

    上面这一部分是,未使用lock关键字的代码。

    结果也如同书上说count是未定。多次启动程序 结果也是不一样的。正确的结果应该是0,加一次见一次正好分别是5K次。

    仔细观察代码,得到这样子的结果也是肯定的。

    首先:

               var NotLockClass = new Counter();
    
                var t1 = new Thread(() =>TestCounter(NotLockClass));
    
                var t2 = new Thread(() => TestCounter(NotLockClass));
    
                var t3= new Thread(() => TestCounter(NotLockClass));

    这个部分,是三个实例的Thread共享了一个Counter的实例化对象。

    那么,要肯定的是这个Counter是一个引用类型

    那么引用类型发挥在哪里呢?

                t1.Start();
    
                t2.Start();
    
                t3.Start();
    
                t1.Join();
    
                t2.Join();
    
                t3.Join();    

    这里。

    首先是T1 T2 T3三个线程启动。

    然后是等待线程完成。

    让结果变得不固定的原因就在这里。

    我们先把方法TestCounter改造一下

       static void TestCounter(CounterBase counterBase)
            {
                for(int i=0;i<10000;i++)
                {
                    counterBase.Increment();//加法
                    Console.WriteLine($"加法启动次序{i}");
                    counterBase.Decrement();//减法
                    Console.WriteLine($"减法启动次序{i}");
                }
            }

    加入一下两个控制台输出语句。

    然后启动程序。

     会发现有很多重复的顺序。而这个就是资源抢夺。

    为了更清楚的看执行的过程,我们再来改造一下。

     static void TestCounter(Counter counter)
            {
                for(int i=0;i<10000;i++)
                {
                    counter.Increment();//加法
                    Console.WriteLine($"加法启动次序{i},当前结果是{counter.Count}");
                    counter.Decrement();//减法
                    Console.WriteLine($"减法启动次序{i},当前结果是{counter.Count}");
                }
            }

    之后在启动

     资源抢夺还是很严重的。 不过你们有没有发现。方法改造之后的结果和正确结果很相近了。不是-1,就是-2.这是一个很有意思的现象。

    如果你将代码改造成:

    class Counter : CounterBase
        {
            public int Count { get; private set; }
            /// <summary>
            /// 非Lock关键字减法
            /// </summary>
            public override void Decrement()
            {
                Console.WriteLine($"减法 当前{Count}");
                Count--;
            }
            /// <summary>
            /// 非lock关键字加法
            /// </summary>
            public override void Increment()
            {
                Console.WriteLine($"加法 当前{Count}");
                Count++;
            }
        }

    结果:

    竟然会出现正确答案,而且几率很高。得到-1这个答案我运行了很多次。-2更是看运气。

    为什么会出现这个现象。我觉得是可以程序的方法可运行时间有一定关系吧。时间可能是多了一下。资源调度上面可能会分配。也可能是线程的原因。这个问题真的很有意思。

    不过还是先放放。

     我们依旧是得到未使用lock关键字,资源抢夺很严重!

    那么我们来看看是使用lock关键字的部分。

    改造代码:

     static void Main(string[] args)
            {
                Console.WriteLine("使用Lock关键字 CounterWidthLock类");
    
                var UseLockClass = new CounterWidthLock();
    
                var t1 = new Thread(() =>TestCounter(UseLockClass));
    
                var t2 = new Thread(() => TestCounter(UseLockClass));
    
                var t3= new Thread(() => TestCounter(UseLockClass));
    
                t1.Start();
    
                t2.Start();
    
                t3.Start();
    
                t1.Join();
    
                t2.Join();
    
                t3.Join();
    
                Console.WriteLine($"最终输出结果是{UseLockClass.Count}");
    
                Console.ReadKey();
            }

     

    肯定是正确答案。

     我们改造一下代码:和上一样:

     static void TestCounter(CounterBase counterBase)
            {
                for(int i=0;i<10000;i++)
                {
                    counterBase.Increment();//加法
                    Console.WriteLine($"加法启动次序{i}");
                    counterBase.Decrement();//减法
                    Console.WriteLine($"减法启动次序{i}");
    
                }
            }

    看一下是否会出现资源抢夺

    其实还是会出现的。

    但是为什么结果会是正确的呢?

    我们再来改造一下:

     static void TestCounter(CounterWidthLock counterwidthlock)
            {
                for(int i=0;i<10000;i++)
                {
                    counterwidthlock.Increment();//加法
                    Console.WriteLine($"加法启动次序{i},当前的结果是{counterwidthlock.Count}");
                    counterwidthlock.Decrement();//减法
                    Console.WriteLine($"减法启动次序{i},当前的结果是{counterwidthlock.Count}");
    
                }
            }

    很明显,执行的顺序还是混乱。但是为什么结果却是正确呢?

    我们来正式的讲讲lock关键字了

    第一步 是lock建立互斥锁。

    第二步执行lock内的方法执行完毕之后,

    第三步释放lock互斥锁。

    这一个过程中,只有一个线程能访问,如果有其他线程访问那就必须等待第一个线程执行完,并释放lock。

    那么在代码中

     1 class CounterWidthLock : CounterBase
     2     {
     3         /// <summary>
     4         /// 判断是否锁定资源
     5         /// </summary>
     6         public readonly object _syncRoot = new object();
     7 
     8         public int Count { get; private set; }
     9 
    10         /// <summary>
    11         /// Lock关键字减法
    12         /// </summary>
    13         public override void Decrement()
    14         {
    15             lock (_syncRoot)//lock关键字,锁定资源
    16             {
    17                 Count--;
    18             }
    19         }
    20         /// <summary>
    21         /// lock关键字加法
    22         /// </summary>
    23         public override void Increment()
    24         {
    25             lock (_syncRoot)//lock关键字,锁定资源
    26             {
    27                 Count++;
    28             }
    29         }
    30     }

    6:锁

    15,25均为lock关键字。

    那么什么是锁?

    可以理解为一个唯一的资源,对象。这个对象最好不是公开的。 公开的话 会造成很多不便。

    也就是T1 T2 T3 虽然都同时进行了TestCounter的方法。

    但因为lock的存在,一个线程在执行加减的时候,其他线程是不可以干预的。也就是T1 执行加减,也许执行了三四次,T2 T3一直在等待。三个线程都是彬彬有礼等待其他来完成他们自己的事情,之后在是自己的。虽然谦让,但也有自己的原则,就是自己在做的时候,别人是不可以干预的

  • 相关阅读:
    LAMP安装配置过程
    【校招面试 之 C/C++】第31题 C++ 11新特性(二)之nullptr关键字
    【校招面试 之 C/C++】第30题 C++ 11新特性(一)之auto关键字
    【校招面试 之 C/C++】第29题 C/C++ 关键字extern
    【校招面试 之 剑指offer】第18题 删除链表中的节点
    【校招面试 之 剑指offer】第17题 打印从1到最大的n位数
    【校招面试 之 剑指offer】第16题 数值的整数次方
    【校招面试 之 剑指offer】第11题 旋转数组中的最小数字
    【Linux 进程】之关于父子进程之间的数据共享分析
    【校招面试 之 剑指offer】第10-3题 矩阵覆盖问题
  • 原文地址:https://www.cnblogs.com/T-ARF/p/9262908.html
Copyright © 2011-2022 走看看