• 多线程数据同步、通信、死锁


      故事发生在前几天,我被对象拖着去看房,对于我这种陈年老宅来说,那就是噩梦啊,虽然有诸多不满,但还是去了。出没于各大新旧楼宇之间,看了一天,要到下午5点左右,终于看好了一个新楼盘,然后看看户型,问问朝向等。都觉得还不错,就准备入手,然后自己还各种科普买房知识,以为准备已充分,合同签了,交了钱,就完事,高高兴兴地就回家了。

      第二天,我对象拿出合同准备再仔细看了看合同内容,然后猛地才发现,合同居然没盖章,这下可把我急着了,这岂不是可以一房多卖,合同都不跟我们盖章,那岂不是合同未生效啊,太气人了,于是赶紧找他们理论,吵吵半天之后盖了章。

      于是乎,我想到了两个问题

      销售员未和我们协商到最后一刻,然后由另一个销售接手(多个线程同时操作同一个数据源)

      合同未完成,就给我们(多线程数据的一致性,lock)

      在多任务系统中,每个独立执行的程序称为进程,也就是正在进行的程序,现在使用的操作系统一般都是多任务的,即能够同时执行多个应用程序,实际情况是,操作系统负责对CPU等设备的资源进行分配和管理,虽然这些设备某一时刻只能做一件事,但以非常小的时间间隔交替执行多个程序,就可以给人以同时执行多个程序的感觉,怎么证明这一点呢?

     1 class TestThread
     2 {
     3     public static void Main(string[] args)
     4     {
     5         Thread t = new Thread(new ThreadStart(new Thread1().Run));
     6         try
     7         {
     8             t.Start();
     9         }
    10         catch (Exception ex)
    11         {
    12             throw ex;
    13         }
    14 
    15         while (true)
    16         {
    17             Console.WriteLine("主线程正在执行...");
    18         }
    19     }
    20 }
     1 class Thread1
     2 {
     3     public void Run()
     4     {
     5         while (true)
     6         {
     7             Console.WriteLine("子线程正在执行...");
     8         }
     9     }
    10 }

    执行结果:可以看到操作系统是轮流分配CPU执行主线程和子线程的

     

    让一个线程中在执行到lock的代码块时不会切换CPU,而是执行完之后才允许切换CPU到另一个线程,保证数据的完整性

    什么情况下会让一房多卖呢,现在我们就用多线程模拟下一房多卖的场景

    假设,有100套房子要卖,同时有四个销售员在卖,此时的代码如下:

     1 class TestThread
     2 {
     3     public static void Main(string[] args)
     4     {
     5         // 一房多卖  
     6         Thread t1 = new Thread(new ThreadStart(new Thread2().Run));
     7         t1.Name = "销售员A";
     8         t1.Start();
     9         Thread t2 = new Thread(new ThreadStart(new Thread2().Run));
    10         t2.Name = "销售员B";
    11         t2.Start();
    12         Thread t3 = new Thread(new ThreadStart(new Thread2().Run));
    13         t3.Name = "销售员C";
    14         t3.Start();
    15         Thread t4 = new Thread(new ThreadStart(new Thread2().Run));
    16         t4.Name = "销售员D";
    17         t4.Start();
    18     }
    19 }
     1 class Thread2
     2 {
     3     public int house = 100;
     4 
     5     // 重点!!!
     6     // string是引用类型,可以当作lock锁定对象,其他值类型是不能用来lock的
     7     public string lockObj = string.Empty;
     8 
     9     // 会抛出异常 error CS0185: “int”不是 lock 语句要求的引用类型
    10     // public int lockInt = 0;
    11 
    12     public void Run()
    13     {
    14         while (true)
    15         {
    16             lock (lockObj)
    17             {
    18                 if (house > 0)
    19                 {
    20                     // 重现存在线程不同步,导致的线程安全问题
    21                     // 解决办法,加入线程同步锁
    22                     Thread.Sleep(10);
    23                     Console.WriteLine(Thread.CurrentThread.Name + " 正在卖 " + house + "号房");
    24                     house--;
    25                 }
    26             }
    27         }
    28     }
    29 }

    执行结果:同一个房子被卖了三次,原因就在于三个销售员,没有用同一个房源,而且互相之间也没有通信,不知道对方卖了那个房子

     接下来我们就要让四个销售员都卖同一个房源,并且要在销售之前加上锁,防止别人和自己同时在卖一套房子

     1 class TestThread
     2 {
     3     public static void Main(string[] args)
     4     {
     5         // 一房一卖
     6         ThreadStart method = new ThreadStart(new Thread2().Run);
     7         Thread t1 = new Thread(method);
     8         t1.Name = "销售员A";
     9         t1.Start();
    10         Thread t2 = new Thread(method);
    11         t2.Name = "销售员B";
    12         t2.Start();
    13         Thread t3 = new Thread(method);
    14         t3.Name = "销售员C";
    15         t3.Start();
    16         Thread t4 = new Thread(method);
    17         t4.Name = "销售员D";
    18         t4.Start();
    19     }
    20 }
     1 class Thread2
     2 {
     3     public int house = 100;
     4 
     5     // 重点!!!
     6     // string是引用类型,可以当作lock锁定对象,其他值类型是不能用来lock的
     7     public string lockObj = string.Empty;
     8 
     9     // 会抛出异常 error CS0185: “int”不是 lock 语句要求的引用类型
    10     // public int lockInt = 0;
    11 
    12     public void Run()
    13     {
    14         while (true)
    15         {
    16             lock (lockObj)
    17             {
    18                 if (house > 0)
    19                 {
    20                     // 重现存在线程不同步,导致的线程安全问题
    21                     // 解决办法,加入线程同步锁
    22                     Thread.Sleep(10);
    23                     Console.WriteLine(Thread.CurrentThread.Name + " 正在卖 " + house + "号房");
    24                     house--;
    25                 }
    26             }
    27         }
    28     }
    29 }

    执行结果:房子一房多卖的情况已被解决

     多个线程互相等待对方的锁定标识未被释放,就会产生死锁

     1 class TestThread
     2 {
     3     public static void Main(string[] args)
     4     {
     5         // 重现死锁问题
     6         Thread3 thread3 = new Thread3();
     7         Thread t1 = new Thread(new ThreadStart(thread3.Run));
     8         t1.Name = "销售员A";
     9         t1.Start();
    10         Thread.Sleep(1);
    11         thread3.lockObj = "中介";
    12         Thread t2 = new Thread(new ThreadStart(thread3.Run));
    13         t2.Name = "销售员B";
    14         t2.Start();
    15     }
    16 }
     1 class Thread3
     2 {
     3     public string lockObj = string.Empty;
     4     public int house = 100;
     5 
     6     public void Run()
     7     {
     8         if (lockObj == "中介")
     9         {
    10             while (true)
    11             {
    12                 lock (this)
    13                 {
    14                     if (house > 0)
    15                     {
    16                         // 重现存在线程不同步,导致的线程安全问题
    17                         // 解决办法,加入线程同步锁
    18                         Thread.Sleep(10);
    19                         // 死锁问题
    20                         lock (lockObj) { }
    21                         Console.WriteLine(lockObj + "" + Thread.CurrentThread.Name + " 正在卖 " + (house--) + "号房");
    22                         ;
    23                     }
    24                 }
    25             }
    26         }
    27         else
    28         {
    29             while (true)
    30             {
    31                 lock (lockObj)
    32                 {
    33                     if (house > 0)
    34                     {
    35                         // 重现存在线程不同步,导致的线程安全问题
    36                         // 解决办法,加入线程同步锁
    37                         Thread.Sleep(10);
    38                         // 死锁问题
    39                         lock (this) { }
    40                         Console.WriteLine("内部的" + Thread.CurrentThread.Name + " 正在卖 " + (house--) + "号房");
    41                     }
    42                 }
    43             }
    44         }
    45     }
    46 }

    执行结果:结果显示,多线程已经卡住了,发生了死锁的情况,原因就是双方都等着对方先出售,自己再卖

    waitOne 告诉当前线程放弃CPU并进入睡眠状态直到其他线程进入同一个监视器并调用Set为止

    多线程之间难免会遇到需要相互打交道的时候,举个例子,开发商为了防止销售员偷懒,就交替为销售员分配客户,下面我们就用代码去模拟下这个场景

     1 class ThreadCommunation
     2 {
     3     public static void Main(string[] args)
     4     {
     5         // 实现销售员A和销售员B交替分配客户
     6         Q q = new Q();
     7 
     8         // 测试多线程之前的通信
     9         new Thread(new ThreadStart(new Producer(q).Run)).Start();
    10         new Thread(new ThreadStart(new Customer(q).Run)).Start();
    11     }
    12 }
     1 /// <summary>
     2 /// DTO
     3 /// </summary>
     4 class Q
     5 {
     6     private EventWaitHandle e = new AutoResetEvent(false);
     7     private readonly object lockA = new object();
     8     private readonly object lockB = new object();
     9     private string seller = "unknown";
    10     private string house = "unknown";
    11     private bool bFull = false;
    12     private bool isStop = false;
    13 
    14     public Q()
    15     {
    16 
    17     }
    18 
    19     public Q(string seller, string house)
    20     {
    21         this.seller = seller;
    22         this.house = house;
    23     }
    24 
    25     public void Stop()
    26     {
    27         this.isStop = true;
    28     }
    29 
    30     public void Put(string seller, string house)
    31     {
    32         // 重点!!!
    33         // 不能用同一个锁定标识,会产生死锁
    34         lock (lockA)
    35         {
    36             if (this.bFull)
    37             {
    38                 // 等待被另一个线程清空后再插入数据
    39                 e.WaitOne();
    40             }
    41             this.seller = seller;
    42             Thread.Sleep(1);
    43             this.house = house;
    44             this.bFull = true;
    45 
    46             // 通知等待的线程可以继续执行
    47             e.Set();
    48         }
    49     }
    50 
    51     public void Get()
    52     {
    53         if (!this.isStop)
    54         {
    55             // 重点!!!
    56             // 不能用同一个锁定标识,会产生死锁
    57             lock (lockB)
    58             {
    59                 if (!this.bFull)
    60                 {
    61                     // 等待被另一个线程清空后再插入数据
    62                     e.WaitOne();
    63                 }
    64                 Console.WriteLine(this.ToString());
    65                 this.bFull = false;
    66 
    67                 // 通知等待的线程可以继续执行
    68                 e.Set();
    69             }
    70         }
    71     }
    72 
    73     public override string ToString()
    74     {
    75         return "销售员:" + this.seller + "正在接待" + this.house + "号客户";
    76     }
    77 }
     1 /// <summary>
     2 /// 生产者
     3 /// </summary>
     4 class Producer
     5 {
     6     private Q q;
     7 
     8     public Producer(Q q)
     9     {
    10         this.q = q;
    11     }
    12 
    13     public void Run()
    14     {
    15         int i = 0;
    16         int house = 100;
    17         while (true)
    18         {
    19             if (house > 0)
    20             {
    21                 if (i == 0)
    22                 {
    23                     q.Put("销售员A", (house--).ToString());
    24                 }
    25                 else
    26                 {
    27                     q.Put("销售员B", (house--).ToString());
    28                 }
    29 
    30                 i = (i + 1) % 2;
    31             }
    32             else
    33             {
    34                 q.Stop();
    35             }
    36         }
    37     }
    38 }
     1 /// <summary>
     2 /// 消费者
     3 /// </summary>
     4 class Customer
     5 {
     6     private Q q;
     7 
     8     public Customer(Q q)
     9     {
    10         this.q = q;
    11     }
    12 
    13     public void Run()
    14     {
    15         while (true)
    16         {
    17             q.Get();
    18         }
    19     }
    20 }

    执行结果:可以看出销售员A和销售员B正在交替接待客户

    总结:理论不是太多,全是实践而来的干货,希望对大家对C#的了解有所帮助

  • 相关阅读:
    javascript中事件
    pku 1836 Alignment
    pku 3086 Triangular Sums
    [转]asp格式化日期
    用数组作checkboxlist数据源
    (转)Membership、MembershipUser和Roles类 详解
    asp中判断 checkbox 是否选中
    使用 AddRange 方法将多个 ListItem 对象添加到集合
    My97DatePicker,一个兼容所有浏览器的日历选择脚本(相当经典)
    .Net下批量删除数据的存储过程问题(用动态SQL )
  • 原文地址:https://www.cnblogs.com/Zyj12/p/11527913.html
Copyright © 2020-2023  润新知