C#多线程之线程同步

作者:.NET开发菜鸟 时间:2022-06-14 19:58:03 

一、前言

我们先来看下面一个例子:

using System;
using System.Threading;

namespace ThreadSynchDemo
{
   class Program
   {
       private static int Counter = 0;
       static void Main(string[] args)
       {
           Thread t1 = new Thread(() => {
               for (int i = 0; i < 1000; i++)
               {
                   Counter++;
                   Thread.Sleep(1);
               }
           });
           t1.Start();

Thread t2 = new Thread(() => {
               for (int i = 0; i < 1000; i++)
               {
                   Counter++;
                   Thread.Sleep(1);
               }
           });
           t2.Start();

Thread.Sleep(3000);
           Console.WriteLine(Counter);
           Console.ReadKey();
       }
   }
}

我们猜想一下程序的输出结果是多少?2000?我们运行程序看一下输出结果:

C#多线程之线程同步

我们看到,程序最后输出的结果跟我们预测的完全不一样,这是什么原因呢?这就是由线程同步引起的问题。

线程同步问题:是解决多个线程同时操作一个资源的问题

在上面的例子中,t1和t2两个线程里面都是让变量Counter的值自增1,假设这时t1线程读取到Counter的值为200,可能t2线程执行非常快,t1线程读取Counter值的时候,t2线程已经把Counter的值改为了205,等t1线程执行完毕以后,Counter的值又被变为了201,这样就会出现线程同步的问题了。那么该如何解决这个问题呢?

二、解决线程同步问题

1、lock

解决线程同步问题最简单的是使用lock。lock可以解决多个线程同时操作一个资源引起的问题。lock是C#中的关键字,它要锁定一个资源,lock的特点是:同一时刻只能有一个线程进入lock的对象的范围,其它lock的线程都要等待。我们看下面优化后的代码:

using System;
using System.Threading;

namespace ThreadSynchDemo
{
   class Program
   {
       private static int Counter = 0;
       // 定义一个locker对象
       private static Object locker = new Object();
       static void Main(string[] args)
       {
           #region 存在线程同步问题
           //Thread t1 = new Thread(() => {
           //    for (int i = 0; i < 1000; i++)
           //    {
           //        Counter++;
           //        Thread.Sleep(1);
           //    }
           //});
           //t1.Start();

//Thread t2 = new Thread(() => {
           //    for (int i = 0; i < 1000; i++)
           //    {
           //        Counter++;
           //        Thread.Sleep(1);
           //    }
           //});
           //t2.Start();
           #endregion

#region 使用Lock解决线程同步问题
           Thread t1 = new Thread(() => {
               for (int i = 0; i < 1000; i++)
               {
                   lock(locker)
                   {
                       Counter++;
                   }
                   Thread.Sleep(1);
               }
           });
           t1.Start();

Thread t2 = new Thread(() => {
               for (int i = 0; i < 1000; i++)
               {
                   lock (locker)
                   {
                       Counter++;
                   }
                   Thread.Sleep(1);
               }
           });
           t2.Start();
           #endregion

Thread.Sleep(3000);
           Console.WriteLine(Counter);
           Console.ReadKey();
       }
   }
}

这时我们在运行程序,查看输出结果:

C#多线程之线程同步

这时输出结果是正确的。

注意:lock只能锁住同一个对象,如果是不同的对象,还是会有线程同步的问题。lock锁定的对象必须是引用类型的对象。

我们在定义一个Object类型的对象,lock分别锁住两个对象,看看是什么结果:

using System;
using System.Threading;

namespace ThreadSynchDemo
{
   class Program
   {
       private static int Counter = 0;
       // 定义一个locker对象
       private static Object locker = new Object();
       // 定义locker2
       private static Object locker2 = new Object();
       static void Main(string[] args)
       {
           #region 存在线程同步问题
           //Thread t1 = new Thread(() => {
           //    for (int i = 0; i < 1000; i++)
           //    {
           //        Counter++;
           //        Thread.Sleep(1);
           //    }
           //});
           //t1.Start();

//Thread t2 = new Thread(() => {
           //    for (int i = 0; i < 1000; i++)
           //    {
           //        Counter++;
           //        Thread.Sleep(1);
           //    }
           //});
           //t2.Start();
           #endregion

#region 使用Lock解决线程同步问题
           //Thread t1 = new Thread(() => {
           //    for (int i = 0; i < 1000; i++)
           //    {
           //        lock(locker)
           //        {
           //            Counter++;
           //        }
           //        Thread.Sleep(1);
           //    }
           //});
           //t1.Start();

//Thread t2 = new Thread(() => {
           //    for (int i = 0; i < 1000; i++)
           //    {
           //        lock (locker)
           //        {
           //            Counter++;
           //        }
           //        Thread.Sleep(1);
           //    }
           //});
           //t2.Start();
           #endregion

#region 使用lock锁住不同的对象也会有线程同步问题
           Thread t1 = new Thread(() => {
               for (int i = 0; i < 1000; i++)
               {
                   lock (locker)
                   {
                       Counter++;
                   }
                   Thread.Sleep(1);
               }
           });
           t1.Start();

Thread t2 = new Thread(() => {
               for (int i = 0; i < 1000; i++)
               {
                   lock (locker2)
                   {
                       Counter++;
                   }
                   Thread.Sleep(1);
               }
           });
           t2.Start();
           #endregion
           Thread.Sleep(3000);
           Console.WriteLine(Counter);
           Console.ReadKey();
       }
   }
}

程序运行结果:

C#多线程之线程同步

可以看到,这时还是会有线程同步的问题。虽然使用了lock,但是我们锁住的是不同的对象,这样也会有线程同步问题。lock必须锁住同一个对象才可以。

我们下面在来看一个多线程同步问题的例子:

using System;
using System.Threading;

namespace ThreadSynchDemo2
{
   class Program
   {
       static int Money = 100;

/// <summary>
       /// 定义一个取钱的方法
       /// </summary>
       /// <param name="name"></param>
       static void QuQian(string name)
       {
           Console.WriteLine(name + "查看一下余额" + Money);
           int yue = Money - 1;
           Console.WriteLine(name + "取钱");
           Money = yue;
           Console.WriteLine(name + "取完了,剩" + Money);
       }

static void Main(string[] args)
       {
           Thread t1 = new Thread(() => {
               for (int i = 0; i < 10; i++)
               {
                   QuQian("t2");
               }
           });
           Thread t2 = new Thread(() => {
               for (int i = 0; i < 10; i++)
               {
                   QuQian("t2");
               }
           });
           t1.Start();
           t2.Start();
           t1.Join();
           t2.Join();
           Console.WriteLine("余额" + Money);
           Console.ReadKey();
       }
   }
}

我们看一下输出结果:

C#多线程之线程同步

可以看到,最终的余额并不是80,这也是线程同步带来的问题,如何解决。解决思路就是使用同步的技术避免两个线程同时修改一个余额。

2、最大粒度&mdash;&mdash;同步方法

在方法上面使用[MethodImpl(MethodImplOptions.Synchronized)],标记该方法是同步方法,这样一个方法只能同时被一个线程访问。我们在QuQian的方法上面标记,修改后的代码如下:

using System;
using System.Runtime.CompilerServices;
using System.Threading;

namespace ThreadSynchDemo2
{
   class Program
   {
       static int Money = 100;

/// <summary>
       /// 定义一个取钱的方法,在上面标记为同步方法
       /// </summary>
       /// <param name="name"></param>
       [MethodImpl(MethodImplOptions.Synchronized)]
       static void QuQian(string name)
       {
           Console.WriteLine(name + "查看一下余额" + Money);
           int yue = Money - 1;
           Console.WriteLine(name + "取钱");
           Money = yue;
           Console.WriteLine(name + "取完了,剩" + Money);
       }

static void Main(string[] args)
       {
           Thread t1 = new Thread(() => {
               for (int i = 0; i < 10; i++)
               {
                   QuQian("t2");
               }
           });
           Thread t2 = new Thread(() => {
               for (int i = 0; i < 10; i++)
               {
                   QuQian("t2");
               }
           });
           t1.Start();
           t2.Start();
           t1.Join();
           t2.Join();
           Console.WriteLine("余额" + Money);
           Console.ReadKey();
       }
   }
}

程序输出结果:

C#多线程之线程同步

现在的方法就是&ldquo;线程安全&rdquo;的了。什么是&ldquo;线程安全&rdquo;呢?&ldquo;线程安全&rdquo;是指方法可以被多个线程随意调用,而不会出现混乱。如果出现了混乱,那么就是&ldquo;线程不安全&rdquo;的。&ldquo;线程安全&rdquo;的方法可以在多线程里面随意的使用。

3、对象互斥锁

对象互斥锁就是我们上面讲的lock。我们在用lock来修改上面QuQian的例子:

using System;
using System.Runtime.CompilerServices;
using System.Threading;

namespace ThreadSynchDemo2
{
   class Program
   {
       static int Money = 100;

/// <summary>
       /// 定义一个取钱的方法,在上面标记为同步方法
       /// </summary>
       /// <param name="name"></param>
       //[MethodImpl(MethodImplOptions.Synchronized)]
       //static void QuQian(string name)
       //{
       //    Console.WriteLine(name + "查看一下余额" + Money);
       //    int yue = Money - 1;
       //    Console.WriteLine(name + "取钱");
       //    Money = yue;
       //    Console.WriteLine(name + "取完了,剩" + Money);
       //}

private static object locker = new object();
       static void QuQian(string name)
       {
           Console.WriteLine(name + "查看一下余额" + Money);
           int yue = Money - 1;
           Console.WriteLine(name + "取钱");
           Money = yue;
           Console.WriteLine(name + "取完了,剩" + Money);
       }

static void Main(string[] args)
       {
           Thread t1 = new Thread(() => {
               for (int i = 0; i < 10; i++)
               {
                   // 使用对象互斥锁
                   lock(locker)
                   {
                       QuQian("t1");
                   }
               }
           });
           Thread t2 = new Thread(() => {
               for (int i = 0; i < 10; i++)
               {
                   lock (locker)
                   {
                       QuQian("t2");
                   }
               }
           });
           t1.Start();
           t2.Start();
           t1.Join();
           t2.Join();
           Console.WriteLine("余额" + Money);
           Console.ReadKey();
       }
   }
}

程序输出结果:

C#多线程之线程同步

可以看到,最终的输出结果还是80。

同一时刻只能有一个线程进入同一个对象的lock代码块。必须是同一个对象才能起到互斥的作用。lock后必须是引用类型,不一定是object,只要是对象就行。

锁对象选择很重要,选不对就起不到同步的作用;选不对还有可能会造成其他地方被锁,比如用字符串做锁(因为字符串缓冲池导致导致可能用的是其他地方正在使用的锁),所以不建议使用字符串做锁。下面的代码就是不允许的:

lock("locker")

两个方法如果都用一个对象做锁,那么访问A的时候就不能访问B,因此锁选择很重要。

4、Monitor

其实lock关键字就是对Monitor的简化调用,lock最终会被编译成Monitor,因此一般不直接使用Monitor类,看下面代码:

using System;
using System.Threading;

namespace MonitorDemo
{
   class Program
   {
       static int Money = 100;
       private static object locker = new object();
       static void QuQian(string name)
       {
           // 等待没有人锁定locker对象,就锁定它,然后继续执行
           Monitor.Enter(locker);
           try
           {
               Console.WriteLine(name + "查看一下余额" + Money);
               int yue = Money - 1;
               Console.WriteLine(name + "取钱");
               Money = yue;
               Console.WriteLine(name + "取完了,剩" + Money);
           }
           finally
           {
               // 释放locker对象的锁
               Monitor.Exit(locker);
           }
       }

static void Main(string[] args)
       {
           Thread t1 = new Thread(() => {
               for (int i = 0; i < 10; i++)
               {
                       QuQian("t1");
               }
           });
           Thread t2 = new Thread(() => {
               for (int i = 0; i < 10; i++)
               {
                       QuQian("t2");
               }
           });
           t1.Start();
           t2.Start();
           t1.Join();
           t2.Join();
           Console.WriteLine("余额" + Money);
           Console.ReadKey();
       }
   }
}

程序输出结果:

C#多线程之线程同步

Monitor类里面还有TryEnter方法,如果Enter的时候有人在占用锁,它不会等待,而是会返回false。看下面的示例代码:

using System;
using System.Threading;

namespace MonitorDemo
{
   class Program
   {
       static int Money = 100;
       private static object locker = new object();
       static void QuQian(string name)
       {
           // 等待没有人锁定locker对象,就锁定它,然后继续执行
           Monitor.Enter(locker);
           try
           {
               Console.WriteLine(name + "查看一下余额" + Money);
               int yue = Money - 1;
               Console.WriteLine(name + "取钱");
               Money = yue;
               Console.WriteLine(name + "取完了,剩" + Money);
           }
           finally
           {
               // 释放locker对象的锁
               Monitor.Exit(locker);
           }
       }

static void F1(int i)
       {
           if (!Monitor.TryEnter(locker))
           {
               Console.WriteLine("有人在锁着呢");
               return;
           }
           Console.WriteLine(i);
           Monitor.Exit(locker);
       }

static void Main(string[] args)
       {
           //Thread t1 = new Thread(() => {
           //    for (int i = 0; i < 10; i++)
           //    {
           //            QuQian("t1");
           //    }
           //});
           //Thread t2 = new Thread(() => {
           //    for (int i = 0; i < 10; i++)
           //    {
           //            QuQian("t2");
           //    }
           //});
           Thread t1 = new Thread(() => {
               for (int i = 0; i < 10; i++)
               {
                   F1(i);
               }
           });
           Thread t2 = new Thread(() => {
               for (int i = 0; i < 10; i++)
               {
                   F1(i);
               }
           });

t1.Start();
           t2.Start();
           t1.Join();
           t2.Join();
           Console.WriteLine("余额" + Money);
           Console.ReadKey();
       }
   }
}

程序输出结果:

C#多线程之线程同步

来源:https://www.cnblogs.com/dotnet261010/p/12335538.html

标签:C#,多线程,线程,同步
0
投稿

猜你喜欢

  • SpringSecurity整合jwt权限认证的全流程讲解

    2022-02-20 09:58:36
  • springboot+jwt+微信小程序授权登录获取token的方法实例

    2022-07-11 17:53:57
  • Android 应用Crash 后自动重启的方法小结

    2021-06-10 11:34:43
  • 1秒钟实现Springboot 替换/写入 word文档里面的文字、图片功能

    2022-05-08 18:35:48
  • Spring Boot统一异常处理最佳实践(拓展篇)

    2023-10-29 16:00:04
  • Java精品项目瑞吉外卖之员工信息管理篇

    2023-07-29 07:43:36
  • Java 实战练手项目之校园超市管理系统的实现流程

    2023-07-09 08:46:55
  • 详解C# Socket异步通信实例

    2022-08-27 14:27:22
  • HorizontalScrollView水平滚动控件使用方法详解

    2023-02-19 21:12:27
  • SpringBoot结合JSR303对前端数据进行校验的示例代码

    2022-09-15 03:22:55
  • Android App开发中ViewPager组件的入门使用教程

    2023-04-30 21:45:34
  • WPF调用ffmpeg实现屏幕录制

    2023-04-23 13:57:00
  • Java使用TCP实现在线聊天的示例代码

    2021-10-16 23:49:53
  • 实例详解SpringBoot默认的JSON解析方案

    2023-07-21 07:34:20
  • c++函数转c#函数示例程序分享

    2023-08-13 14:45:26
  • 一文详解Android无需权限调用系统相机拍照

    2022-06-22 03:08:26
  • 有关微博content的封装实现详解

    2022-12-02 17:37:53
  • 详解升级Android Studio3.0时遇到的几个问题

    2021-11-19 08:13:52
  • Flutter深色模式适配的实现

    2023-01-22 12:41:03
  • C#实现员工ID卡的识别功能

    2021-08-11 23:49:25
  • asp之家 软件编程 m.aspxhome.com