15.3.9 线程同步

版权声明:本文为博主原创文章,转载请在显著位置标明本文出处以及作者网名,未经作者允许不得用于商业目的。

线程同步是指多个线程访问某个资源时,线程之间排队访问,即当一个线程访问资源时候,别的线程不能访问,直到该线程使用资源完成后,其他线程才可使用,避免使用资源时产生混乱。

【例 15.18【项目:code15-018】多线程未使用线程同步。

        static void Main(string[] args)

        {

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

            {

                Thread thDoLoop = new Thread(doLoop);

                thDoLoop.Start(i);

            }

            Thread.Sleep(1000);

            Console.ReadKey();

        }

        static void doLoop(object number)

        {

            int counter = (int)number;

            for (int i = 1; i <= 100; i++)

                Console.Write(" " + counter);

        }

运行结果如下图所示:

图15-18 未使用线程同步造成混乱

可以看到程序的输出结果很混乱,这是由于线程是由系统控制,我们也不知道在哪个位置,系统就将线程2停了启动了线程0,接着又将线程0停了,切换运行线程3……。本来想完整地输出100个0、100个1……,显示是不可能的。想要输出期望的结果,就只有实现线程的同步才行。

15.3.9.1 使用lock语句实现线程同步

lock语句可确保多个线程不在同一时间执行语句块,它最常见作用是保护数据不被多个线程同时更新。如果线程中操作某个资源的语句必须在没有中断的情况下完成,则将这些语句放入lock代码块。它的使用方法如下:

lock(lockobject)

    {

            //独占时要执行的语句块

}

注意:尽量缩小锁的范围,只锁定必要的代码块,以减少线程等待时间;确保锁的顺序一致,避免多个线程以不同顺序获取多个锁。

需要注意的是,lockobject应该满足以下条件:

1、值不能是null;

2、必须是object类型及其派生类,通常使用 object 类型是最简单和最通用的方式;

3、不能更改lockobject的值;

4、如果多个线程需要同步访问共享资源,lockobject对象必须是静态的;

5、建议使用私有的、局部的 object 实例作为锁。

【例 15.19【项目:code15-019】使用SyncLock实现线程同步。

        static object lockObj;

        static void Main(string[] args)

        {

            lockObj = new object();

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

            {

                Thread thDoLoop = new Thread(doLoop);

                thDoLoop.Start(i);

            }

            Thread.Sleep(1000);

            Console.ReadKey();

        }

        static void doLoop(object number)

        {

            //锁定

            lock(lockObj)

            {

                for (int i = 1; i <= 100; i++)

                    Console.Write(" " + number.ToString());

            }

        }

运行结果如下图所示:

图15-19 使用SyncLock实现线程同步

注意:虽然看着是按照0-5的顺序调用线程,但是线程实际的先后顺序是由系统控制,因此输出并不是按照0-5的顺序。

15.3.9.2 使用Monitor类实现线程的同步

lock是一种简单处理同步的方法。VB.Net还提供了Monitor类处理线程同步。

Monitor类比较重要的几个静态方法:

  1. Enter:在指定对象上获取排他锁。
  2. Exit:释放指定对象上的排他锁。
  3. TryEnter:尝试获取指定对象上的排他锁,并返回设置一个值,指示是否获取了该锁。

Monitor的Enter、TryEnter和Exit的用法有点类似于lock语句。使用Enter或者TryEnter时,锁定的对象所需要满足的条件和lock 的lockobject一样,具体请参看第15.3.9.1节。

【例 15.20【项目:code15-020】使用Monitor实现线程同步。

本例中用到TryEnter方法的一个重载:

public static bool TryEnter (object obj, int millisecondsTimeout);

参数说明:

  1. obj:在其上获取锁的对象。
  2. millisecondsTimeout:等待锁所需的毫秒数,即在millisecondsTimeout毫秒后释放这个锁,不会一直占有,这样其他线程也可以继续下去。如果millisecondsTimeout设置为-1,这个方法就相当于Enter(Object);如果millisecondsTimeout设置为0,这个方法就相当于TryEnter(Object)。

具体代码如下:

        private static object MonitorObj;

        static void Main(string[] args)

        {

            MonitorObj = new object();

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

            {

                Thread thDoLoop = new Thread(doLoop);

                thDoLoop.Start(i);

            }

            Console.ReadKey();

        }

        static void doLoop(object number)

        {

            Monitor.TryEnter(MonitorObj, 10);

            for (int i = 1; i <= 100; i++)

                Console.Write(" " + number.ToString());

            Monitor.Exit(MonitorObj);

        }

这个代码运行大概率会出错,Monitor.Exit 引发SynchronizationLockException错误,并显示消息“从不同步的代码块中调用了对象同步方法”,而且查看控制台应用程序窗口,也会看到显示的结果也不对。

可以理解为,某个线程调用lock或者Monitor.Enter会一直阻塞其它线程,直至lock结束或调用Monitor.Exit后,其余线程才能继续进入锁定的语句Monitor.TryEnter只会阻塞到设定的时间,当设定的时间到后,其余线程都可以调用锁定的语句,Monitor.TryEnter在其余线程上都被调用,一是造成了线程都进入,相当于都又在同时运行抢资源;二是每个线程都调用了Monitor.TryEnter,但是最先的线程调用Monitor.Exit后,实际已经将锁释放了,后面的线程想再次释放一个不存在的锁,因而引发程序错误。

解决的方法是预计线程可能占用时间,增加获取锁的时间,上例中可以将以下语句:

     Monitor.TryEnter(MonitorObj, 10)

修改为

     Monitor.TryEnter(MonitorObj, 100)

更多的情况下,TryEnter用于防止抢资源出现混乱。举个简单例子,银行只开了一个通道办理业务,现在有两个人需要通过这个通道从银行取款,显然一个时刻只能有一个人能够办理业务,另外一个人要么因为等待时间太长还有急事来不及办理就走了,要么只有等到这个人取款完毕。如果两人同时办理肯定会引发混乱。这个例子的两种情况,使用TryEnter都能够解决。

【例 15.21【项目:code15-021】第一种情况:抢不到资源的线程超时后不再抢资源。

        private static object MonitorObj;

        static void Main(string[] args)

        {

            //【例 15.20

        }

        static void doLoop(object number)

        {

            if (Monitor.TryEnter(MonitorObj, 10))

            {

                try

                {

                    for (int i = 1; i <= 100; i++)

                        Console.Write(" " + number);

                }

                catch (Exception ex)

                {

                    Console.WriteLine(ex.Message);

                }

                finally

                {

                    Monitor.Exit(MonitorObj);

                }

            }

        }

运行结果如下图所示:

图15-20 并非所有的线程都能够输出

图15-27可看到本来应该输出124的三个线程由于在指定时间内没有抢到资源,因此并没有在控制台中输出数据。

【例 15.22【项目:code15-022】第二种情况:使用TryEnter等待其它线程结束。

        private static object MonitorObj;

        static void Main(string[] args)

        {

            //【例 15.20

        }

        static void doLoop(object number)

        {

            while (!(Monitor.TryEnter(MonitorObj, 10)))

            {

                //Thread.Sleep(10);

            }

            for (int i = 1; i <= 100; i++)

                Console.Write(" " + number);

            Thread.Sleep(100);

            Monitor.Exit(MonitorObj);

        }

采用循环不断地检测是否获得了资源,如果获得资源则继续运行下面的语句。运行的结果同【例 15.19】

 

学习更多vb.net知识,请参看vb.net 教程 目录

学习更多C#知识,请参看C#教程 目录

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

.Net学习

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值