• 线程同步和通信


    NET中各种线程同步方法


    在NET多线程开发中,有时候需要多个线程协调工作,完成这个步骤的过程称为“同步”。 

    使用同步的主要原因: 
    1.多个线程访问同一个共享资源。 
    2.多线程写入文件时保证只有一个线程使用文件资源。 3.由事件引发线程,线程等待事件,需要挂起线程。 

    NET中线程同步常见的几种方法:

    1.lock


    lock 确保当一个线程位于代码的临界区时,另一个线程不进入临界区。如果其他线程试图进入锁定的代码,则它将一直等待(即被阻止),直到该对象被释放。 
    lock的优点:简单易用,对象的同步几乎透明,轻量级。 

    使用lock需要注意: 
    锁定的对于应该是私有的,如果是公有的对象,可能出现超出控制范围的其它代码锁定该对象。 
    所以应该尽量避免使用lock(this),不保证会有其他线程闯入破坏数据正确性。 

    一个lock(this)错误的示例:

    class Program
    {
        static void Main(string[] args)
        {
            A a1 = new A();
            A a2 = new A();
            Thread T1 = new Thread(new ParameterizedThreadStart(a1.Test));
            Thread T2 = new Thread(new ParameterizedThreadStart(a2.Test));
            T1.Start(2);
            T2.Start(5);
            Console.Read();
        }
    }
    public class A
    {
        public static Int32 Count = 2;
        public void Test(object i)
        {
            lock (this)
            {
                Console.WriteLine("Count={0}", Count);
                Count += (Int32)i;
                Thread.Sleep(1 * 1000);
                Console.WriteLine("i={0},?Count+i={1}", i, Count);
                Console.WriteLine("--------------------------");
            }
        }
    }

    图片1 
    这里数据和我们期待的不相同。

    这里lock锁定的是A的实例,当线程T1执行的时候,lock锁定了a1对象,所以线程t2可以执行a2对象的Test方法。

    正确的写法:

    public class A
    {
        private static object obj = new object();
        public static Int32 Count = 2;
        public void Test(object i)
        {
            lock (obj)
            {
                Console.WriteLine("Count={0}", Count);
                Count += (Int32)i;
                Thread.Sleep(1 * 1000);
                Console.WriteLine("i={0},?Count+i={1}", i, Count);
                Console.WriteLine("--------------------------");
            }
        }
    }

    图片2

    这里的线程已经正常工作了,Count也正常的累加。


    lock (obj)怎么错误了? 
    上面正常运行的程序稍微改动下!

    class Program
    {
        static void Main(string[] args)
        {
            A a1 = new A();
            A a2 = new A();
            Thread T1 = new Thread(new ParameterizedThreadStart(a1.Test));
            Thread T2 = new Thread(new ParameterizedThreadStart(a2.Test));
            T1.Start(2);
            T2.Start(5);
            Console.Read();
        }
    }
    public class A
    {
        private object obj = new object();
        public static Int32 Count = 2;
        public void Test(object i)
        {
            lock (obj)
            {
                Console.WriteLine("Count={0}", Count);
                Count += (Int32)i;
                Thread.Sleep(1 * 1000);
                Console.WriteLine("i={0},?Count+i={1}", i, Count);
                Console.WriteLine("--------------------------");
            }
        }
    }

    图片3

    分析下:这里我们把

    private static object obj = new object();

    中的static去掉了,所以obj变成了私有的实例成员,a1,a2都有不同的obj实例,所以lock这里也就没什么作用了! 

    让lock不再错下去! 
    这里我们也不再把static写上去了,只需要把调用那里稍微改动下。

    class Program
    {
        static void Main(string[] args)
        {
            A a1 = new A();
            Thread T1 = new Thread(new ParameterizedThreadStart(a1.Test));
            Thread T2 = new Thread(new ParameterizedThreadStart(a1.Test));
            T1.Start(2);
            T2.Start(5);
            Console.Read();
        }
    }

    图片4

    我们这里让a2对象去见马克思了,a1对象的Test调用了2次,这里lock锁定的obj都是a1的同一个私有对象obj,所以lock是起作用的!

    Monitor


    Monitor实现同步比lock复杂点,lock实际上是Monitor的简便方式,lock最终还是编译成Monitor。 
    不同处: 
    1.Monitor在使用的时候需要手动指定锁和手动释放手。 
    2.Monitor比lock多了几个实用的方法

    public static bool Wait(object obj);
    public static void Pulse(object obj);
    public static void PulseAll(object obj);


    Mointor同步实例:

    class Program
    {
        static void Main(string[] args)
        {
            Int32[] nums = { 1, 2, 3, 4, 5 };
            SumThread ST1 = new SumThread("Thread1");
            SumThread ST2 = new SumThread("Thread2");
            ST1.Run(nums);
            ST2.Run(nums);
            Console.Read();
        }
    }
    
    public class SumThread
    {
        //注意这里私有静态SA是SumThread共有的,所以考虑同步问题
        private static SumArray SA = new SumArray();
        private Thread t;
    
        public SumThread(string ThreadName)
        {
            t = new Thread((object nums) =>
            {
                Console.WriteLine("线程{0}开始执行...", t.Name);
                Int32 i = SA.GetSum((Int32[])nums);
                Console.WriteLine("线程{0}执行完毕,sum={1}", t.Name, i);
            });
            t.Name = ThreadName;
        }
    
        public void Run(Int32[] nums)
        {
            t.Start(nums);
        }
    }
    
    public class SumArray
    {
        private Int32 sum;
        private  object obj = new object();
    
        public Int32 GetSum(Int32[] nums)
        {
            Monitor.Enter(obj);
            try
            {
                //初始化sum值,以免获取到其它线程的脏数据
                sum = 0;
                foreach (Int32 num in nums)
                {
                    sum += num;
                    Console.WriteLine("当前线程是:{0},sum={1}", Thread.CurrentThread.Name, sum);
                    Thread.Sleep(1 * 1000);
                }
                return sum;
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
                return 0;
            }
            finally
            {
                //很重要,千万不要忘记释放锁
                Monitor.Exit(obj);
            }
        }
    }

    图片5


    这里线程之间和蔼可亲的执行着,没有去抢着计算。 
    试着把

    Monitor.Enter(obj);
    Monitor.Exit(obj);

    去掉,那么线程就不会这么听话了!

    图片6

    另外一种同步的写法

    前面使用的同步方式并不是所有情况都适合的,假如我们调用的是第三方的组件,我们没有修改源码的权限,那么我们只有考虑下面这种同步的实现。

    class Program
    {
        static void Main(string[] args)
        {
            Int32[] nums = { 1, 2, 3, 4, 5 };
            SumThread ST1 = new SumThread("Thread1");
            SumThread ST2 = new SumThread("Thread2");
            ST1.Run(nums);
            ST2.Run(nums);
            Console.Read();
        }
    }
    
    public class SumThread
    {
        //注意这里私有静态SA是SumThread共2有的,所以考虑同步问题
        private static SumArray SA = new SumArray();
        private Thread t;
    
        public SumThread(string ThreadName)
        {
            t = new Thread((object nums) =>
            {
                Console.WriteLine("线程{0}开始执行...", t.Name);
                Monitor.Enter(SA);
                try
                {
                    Int32 i = SA.GetSum((Int32[])nums);
                    Console.WriteLine("线程{0}执行完毕,sum={1}", t.Name, i);
                }
                catch (Exception e)
                {
                    Console.WriteLine(e.Message);
                }
                finally
                {
                    //很重要,千万不要忘记释放锁
                    Monitor.Exit(SA);
                }
            });
            t.Name = ThreadName;
        }
    
        public void Run(Int32[] nums)
        {
            t.Start(nums);
        }
    }
    
    public class SumArray
    {
        private Int32 sum;
    
        public Int32 GetSum(Int32[] nums)
        {
    
            //初始化sum值,以免获取到其它线程的脏数据
            sum = 0;
            foreach (Int32 num in nums)
            {
                sum += num;
                Console.WriteLine("当前线程是:{0},sum={1}", Thread.CurrentThread.Name, sum);
                Thread.Sleep(1 * 1000);
            }
            return sum;
        }
    }

    图片7

    注意:这里锁定的是SA.GetSum()调用本身,不是方法内部代码,SA对象是私有的。

    Monitor-线程通信


    当线程T正在lock块执行,需要访问另外一个线程lock块中的资源R,这个时候如果T等待R可用,有可能会让线程进入死循环。 
    这里就可以使用线程通信,先让T暂时放弃对lock块中的控制,等R变得可用,那么就通知线程T恢复运行。

    public static bool Wait(object obj);
    public static void Pulse(object obj);
    public static void PulseAll(object obj);

    上面就是这里需要用的方法,属于Monitor。 Wait是让线程暂停,这个方法有个重写,多了一个参数指定暂停的毫秒数。 
    Pulse是唤醒线程。 
    时钟滴答例子:

    class Program
    {
        static void Main(string[] args)
        {
            Clock C = new Clock();
            C.RunClock(1);
            Console.Read();
        }
    }
    public class Clock
    {
        private object obj = new object();
    
        //开始运行时钟,输入运行分钟
        public void RunClock(Int32 Minute)
        {
            Thread T1 = new Thread((object Minute1) =>
            {
                Int32 m = Convert.ToInt32(Minute1) * 60 / 2;
                while (m > 0)
                {
                    DI(true);
    
                    m--;
                }
            });
            Thread T2 = new Thread((object Minute1) =>
            {
                Int32 m = Convert.ToInt32(Minute1) * 60 / 2;
                while (m > 0)
                {
                    DA(true);
                    m--;
                }
            });
            T1.Start(Minute);
            T2.Start(Minute);
        }
    
        public void DI(bool run)
        {
            lock (obj)
            {
                Console.WriteLine("嘀");
                Thread.Sleep(1000);
                Monitor.Pulse(obj);//执行完毕,唤醒其它线程
                Monitor.Wait(obj);//进入暂停,移交执行权利,等待唤醒
            }
        }
    
        public void DA(bool run)
        {
            lock (obj)
            {
                Console.WriteLine("嗒");
                Thread.Sleep(1000);
                Monitor.Pulse(obj);//执行完毕,唤醒其它线程
                Monitor.Wait(obj);//进入暂停,移交执行权利,等待唤醒
            }
        }
    }

    图片8

  • 相关阅读:
    正则表达式
    浏览器加载时间线
    浏览器事件
    脚本化CSS
    定时器元素大小位置属性等 20181231
    关于行内元素 20181229
    个人冲刺01
    周总结
    团队冲刺10
    团队冲刺09
  • 原文地址:https://www.cnblogs.com/zhcw/p/2538322.html
Copyright © 2020-2023  润新知