多线程--Lock (C#)

8 篇文章 0 订阅

一.简述

使用多线程时,不可避免的会遇到线程安全的问题,从而需要为使用多线程代码的安全执行考虑

二.注意点

  1. lock只对多线程有效,对单线程无效,单线程lock不会导致死锁
  2. 不推荐使用lock(this),因为在它外部也可以访问它
  3. 不应该使用lock(string(类型)),因为string在内存分配上是重用的,可能会导致冲突
  4. lock中包含的代码最好不要太多,因为在这里是单线程运行的
  5. .net提供了一些线程安全的集合类,使用这些集合不需要用到lock
  6. 在可以使用数据分拆的方法来使用多线程时,最好使用数据分拆而不使用lock
  7. lock的对象应该是 private static readonly object Object_Lock = new object();

三.简单案例

  • lock只对多线程有效,对单线程无效 ,单线程lock不会导致死锁

class Program
{
    static void Main(string[] args)
    {
        try
        {
            Console.WriteLine("RecursiveCall call start");
            RecursiveCall();
            Console.WriteLine("RecursiveCall call end");
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
        Console.ReadKey();
    }

    private static object Object_Lock = new object();
    private static int Num = 0;

    /// <summary>
    /// 单线程lock测试
    /// </summary>
    private static void RecursiveCall()
    {
        lock (Object_Lock)
        {
            Num++;
            if (Num <= 5)
            {
                //输出Num-当前线程id-当前时间
                Console.WriteLine($"Num:{Num} , ThreadId:{Thread.CurrentThread.ManagedThreadId} ,  {DateTime.Now.ToString("yyyy - MM - dd HH: mm:ss.fff")}"); //$在C# 6 方可使用

                Thread.Sleep(1000);
                //递归调用,所以会多次访问lock (Object_Lock)
                RecursiveCall();
            }
        };
    }
}

调用RecursiveCall方法之后控制台输出如下:

结论:从控制台输出结果可知,单线程可以多次进入lock (Object_Lock)区域,所以不用担心lock锁主单线程导致死锁的情况

  • 不推荐使用lock(this),因为在它外部也可以访问它

class Program
{
    static void Main(string[] args)
    {
        try
        {
            ThisLockTest lockTest = new ThisLockTest();
            //开启一个任务在2秒钟之后执行
            Task.Delay(2000).ContinueWith(t =>
            {
                lock (lockTest)
                {
                    Console.WriteLine($"ThreadId:{Thread.CurrentThread.ManagedThreadId} ,  {DateTime.Now.ToString("yyyy - MM - dd HH: mm:ss.fff")}");
                }
            });
            //调用测试类方法
            lockTest.Method();
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
        Console.ReadKey();
    }
}

/// <summary>
/// This lock测试类
/// </summary>
public class ThisLockTest
{
    private int num = 0;
    public void Method()
    {
        lock (this)
        {
            //循环5次,输出相应的信息,每次停止1秒钟,耗时大约5秒
            while (num < 5)
            {
                Console.WriteLine($"{num++} , ThreadId:{Thread.CurrentThread.ManagedThreadId} ,  {DateTime.Now.ToString("yyyy - MM - dd HH: mm:ss.fff")}");
                Thread.Sleep(1000);
            }
        }
    }
}

控制台输出如下:

结论:正常理解应该是在第2或者3行就输出ThreadId:4的信息,但却不是,这里就是因为lock(lockTest)和lock(this)中的 lockTest和this是同一对象,也就是说两个lock锁的是同一对象,只是名称不同而已,所以才导致同步输出了

  • 不应该使用lock(string(类型)),因为string在内存分配上是重用的,可能会导致冲突

/// <summary>
/// 定义两个相同字符串的 lock
/// </summary>
public static string StringLock = "String";
public static string StringLock_New = "String";

class Program
{
    static void Main(string[] args)
    {
        try
        {
           ///启动两个线程执行任务
           ///两个任务分别是在进入lock之前输出即将进入lock区域,在进入lock区域时输出已经进入,在退出lock时输出即将退出lock区域
           Task.Run(() => 
           {
               Console.WriteLine($"ThreadId:{Thread.CurrentThread.ManagedThreadId} 即将进入lock区域 ,  {DateTime.Now.ToString("yyyy - MM - dd HH: mm:ss.fff")}");
               lock(StringLock)
               {
                   Console.WriteLine($"ThreadId:{Thread.CurrentThread.ManagedThreadId} 已经进入lock区域 ,  {DateTime.Now.ToString("yyyy - MM - dd HH: mm:ss.fff")}");
                   //停止5秒钟
                   Thread.Sleep(5000);

                   Console.WriteLine($"ThreadId:{Thread.CurrentThread.ManagedThreadId} 即将出lock区域 ,  {DateTime.Now.ToString("yyyy - MM - dd HH: mm:ss.fff")}");
               }
           });

           Task.Run(() =>
           {
               Console.WriteLine($"ThreadId:{Thread.CurrentThread.ManagedThreadId} 即将进入lock区域 ,  {DateTime.Now.ToString("yyyy - MM - dd HH: mm:ss.fff")}");
               lock (StringLock_New)
               {
                   Console.WriteLine($"ThreadId:{Thread.CurrentThread.ManagedThreadId} 已经进入lock区域 ,  {DateTime.Now.ToString("yyyy - MM - dd HH: mm:ss.fff")}");
                   //停止5秒钟
                   Thread.Sleep(5000);

                   Console.WriteLine($"ThreadId:{Thread.CurrentThread.ManagedThreadId} 即将出lock区域 ,  {DateTime.Now.ToString("yyyy - MM - dd HH: mm:ss.fff")}");
               }
           });

        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
        Console.ReadKey();
    }
}

控制台输出如下:

从控制台输出红色方格中可以看出之后登记第一个线程退出了lock之后,第二个线程才能进入lock,这说明了lock()的是同一个对象,所以推荐不应该使用string类型的lock,因为两个string对象可能相同(享元模式)

  • lock中包含的代码最好不要太多,因为在这里是单线程运行的

这里就不用举例子了,想一想就能够知道,一个线程执行了越多的代码,执行使用可能就越长,那么在lock外面等待的线程就要一直等,这样用多线程就没太大的作用了

  • .net提供了一些线程安全的集合类,使用这些集合不需要用到lock

在System.Collections.Concurrent命名空间下的集合类都是线程安全的,就不需要使用lock了

  • 在可以使用数据分拆的方法来使用多线程时,最好使用数据分拆而不使用lock

对于多任务的程序,如果这些任务可以分开进行,那么最好是使用数据分拆的方法执行,而不是使用lock(待更新)

 

 

  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值