UnityC#的lock用法简记

news2024/9/20 16:38:41

UnityC#的lock用法简记

    • 简述
    • 代码实例
      • 一、单线程
      • 二、多线程无lock
      • 三、多线程使用lock
    • 死锁
    • 注意
    • 拓展
      • lock->Invoke
      • Monitor
    • 参考链接

简述

多线程环境中,不使用lock锁,会形成竞争条件,导致错误。
使用lock锁可以保证当有线程操作某个共享资源时,能使该代码块按照指定的顺序执行,其他线程必须等待直到当前线程完成操作。
即是多线程环境,如果一个线程锁定了共享资源,需要访问该资源的其他线程则会处于阻塞状态,并等待直到该共享资源接触锁定。

private object o = new object();//创建一个对象
public void Work()
{
  lock(o)//锁住这个对象
  {
    //做一些必须按照顺序做的事情
  }
}

代码实例

一、单线程

看此代码,是从上面开始执行,先执行A,再执行B,这就是单线程程序,按照顺序执行的,此时结果是可以控制的。

using System.Threading;
using UnityEngine;

public class Program : MonoBehaviour
{
    static int a = 0;
    static int b = 0;
    private static object o = new object();

    void Start()
    {
        A();
        B();
    }

    private static void A()
    {
        a += 2;
        Debug.Log("我是A方法,a=" + a);
        Thread.Sleep(5000);//暂停5秒
        b += 2;
        Debug.Log("我是A方法,b=" + b);
    }
    private static void B()
    {
        b++;
        Debug.Log("我是B方法,b=" + b);
        Thread.Sleep(1000); //暂停1秒
        a++;
        Debug.Log("我是B方法,a=" + a);
    }
}

结果

二、多线程无lock

我们增加了多线程,就是让A和B方法同时执行,此时,结果就是不可控制的。有时候先执行B方法,有时候先执行A方法。

void Start()
{
    //A();
    //B();
    Thread t1 = new Thread(A);
    Thread t2 = new Thread(B);
    t1.Start();
    t2.Start();
}

先执行A方法 :
A
先执行B方法:
B
对于为什么先执行A,后执行B,或者先执行B,后执行A,这个是操作系统根据CPU自动计算出来的。可见,我们的问题就来了。能不能这样,既能多线程执行,又可控制A和B的顺序呢?这个就要用到lock了。

三、多线程使用lock

所以,我们要的效果就是,在多线程的情况下,要么先执行A,要么先执行B。不能让A和B进行嵌套执行,必须按照顺序。程序一旦进入lock,那么就锁住,锁住的这段代码,此时只能有一个线程去访问,只有等这个线程访问结束了,其他线程才能访问。
为了增加对比,我们再增加一个C方法:

using System.Threading;
using UnityEngine;

public class Program : MonoBehaviour
{
    static int a = 0;
    static int b = 0;
    private static object o = new object();

    void Start()
    {
        //A();
        //B();
        Thread t1 = new Thread(A);
        Thread t2 = new Thread(B);
        t1.Start();
        t2.Start();
        Thread t3 = new Thread(C);
        t3.Start();
    }

    private static void A()
    {
        lock (o)
        {
            a += 2;
            Debug.Log("我是A方法,a=" + a);
            Thread.Sleep(5000);//暂停5秒
            b += 2;
            Debug.Log("我是A方法,b=" + b);
        }
    }
    private static void B()
    {
        lock (o)
        {
            b++;
            Debug.Log("我是B方法,b=" + b);
            Thread.Sleep(1000); //暂停1秒
            a++;
            Debug.Log("我是B方法,a=" + a);
        }
    }
    private static void C()
    {
        Debug.Log("我是C方法,随机出现");
    }
}

结果:

  1. C随机出现;先A,再B。
    结果一
  2. A先运行;再随机除了C,因为C没有被lock,所以C没有得到控制;最后完成了A;再运行B。
    结果二
  3. C随机出现;先B,再A。
    结果三

死锁

使用lock时注意共享资源使用,不然可能造成deadlock
使用monitor类 其拥有TryEnter方法,该方法接收一个超时参数。如果我们能够在获取被lock保护的资源之前,超时参数过期。则该方法会返回false

注意

我们lock的一般是对象,不是值类型和字符串。

  1. 为什么不能lock值类型?
    比如lock(1)呢?lock本质上Monitor.EnterMonitor.Enter会使值类型装箱,每次lock的是装箱后的对象。lock其实是类似编译器的语法糖,因此编译器直接限制住不能lock值类型。退一万步说,就算能编译器允许你lock(1),但是object.ReferenceEquals(1,1)始终返回false因为每次装箱后都是不同对象),也就是说每次都会判断成未申请互斥锁,这样在同一时间,别的线程照样能够访问里面的代码,达不到同步的效果。同理lock((object)1)也不行。
  2. lock字符串?
    那么lock("xxx")字符串呢?MSDN上的原话是:

锁定字符串尤其危险,因为字符串被公共语言运行库 (CLR)“暂留”。 这意味着整个程序中任何给定字符串都只有一个实例,同一个对象表示了所有运行的应用程序域的所有线程中的该文本。因此,只要在应用程序进程中的任何位置处具有相同内容的字符串上放置了锁,就将锁定应用程序中该字符串的所有实例。

  1. MSDN推荐的lock对象
    通常,最好避免锁定public类型或锁定不受应用程序控制的对象实例。例如,如果该实例可以被公开访问,则lock(this)可能会有问题,因为不受控制的代码也可能会锁定该对象。这可能导致死锁,即两个或更多个线程等待释放同一对象。出于同样的原因,锁定公共数据类型(相比于对象)也可能导致问题。
    而且lock(this)只对当前对象有效,如果多个对象之间就达不到同步的效果。
    自定义类推荐用私有的只读静态对象,比如:private static readonly object obj = new object();
    为什么要设置成只读的呢?这是因为如果在lock代码段中改变obj的值,其它线程就畅通无阻了,因为互斥锁的对象变了,object.ReferenceEquals必然返回false

拓展

lock->Invoke

C#的环境.Net Framenwok 4.8。
在主线程和线程通使用lock同步Invoke(new Action(() =>{})会导致死锁
多线程处理先加锁后委托会死锁:

lock (logShowLock)
{
    Invoke(new Action(() => { Console.WriteLine("test"); }));
}

优化方法:

Invoke(new Action(() =>
{
    lock (logShowLock)
    {
        Console.WriteLine("test");
    }
}));

Monitor

其实lock相当于Monitor

lock(o);
{
	do 
}

等价于

Monitor.Enter(o);
{
	do
}
Monitor.Exit(o);

真正实现了线程同步功能的,就是System.Threading.Monitor类型,lock关键字只是用来代替调用EnterExit方法。
Enter相当于进入这个代码块,Exit是退出这个代码块。当这个代码块再运行的时候,其他线程就不能访问。Monitor中的{}可以去掉,不影响。
可以将上面lock示例修改为Monitor,如下:

using System.Threading;
using UnityEngine;

public class Program : MonoBehaviour
{
    static int a = 0;
    static int b = 0;
    private static object o = new object();

    void Start()
    {
        //A();
        //B();
        Thread t1 = new Thread(A);
        Thread t2 = new Thread(B);
        t1.Start();
        t2.Start();
        Thread t3 = new Thread(C);
        t3.Start();
    }

    private static void A()
    {
        Monitor.Enter(o);
        {
            a += 2;
            Debug.Log("我是A方法,a=" + a);
            Thread.Sleep(5000);//暂停5秒
            b += 2;
            Debug.Log("我是A方法,b=" + b);
        }
        Monitor.Exit(o);
    }
    private static void B()
    {
        Monitor.Enter(o);
        {
            b++;
            Debug.Log("我是B方法,b=" + b);
            Thread.Sleep(1000); //暂停1秒
            a++;
            Debug.Log("我是B方法,a=" + a);
        }
        Monitor.Exit(o);
    }
    private static void C()
    {
        Debug.Log("我是C方法,随机出现");
    }
}

参考链接

  1. https://blog.csdn.net/u012563853/article/details/124767902
  2. https://www.bbsmax.com/A/Gkz1xx0GdR/
  3. https://blog.csdn.net/u011555996/article/details/103916175
  4. https://blog.csdn.net/weixin_44193637/article/details/127617322

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/172526.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【ONE·C || 操作符详解】

总言 C语言:各种操作符的使用介绍。 文章目录总言1、算术操作符2、移位操作符2.1、整体介绍2.2、左移操作符2.3、右移操作符(逻辑右移、算术右移)3、位操作符3.1、整体介绍3.2、演示实例3.2.1、按位与3.2.2、按位或3.2.3、按位异或3.2.4、按位…

离线文章画像计算--Tfidf计算

2.4.2 Tfidf计算 2.4.2.1 目的 计算出每篇文章的词语的TFIDF结果用于抽取画像 2.4.2.2TFIDF模型的训练步骤 读取N篇文章数据文章数据进行分词处理TFIDF模型训练保存,spark使用count与idf进行计算利用模型计算N篇文章数据的TFIDF值 2.4.2.3 实现 想要用TFIDF进行…

【数据结构初阶(Java)】认识时间复杂度和空间复杂度

目录 前言: 1、算法效率 2、时间复杂度 1、大O的渐近表示法(不是一个准确的) 2、时间复杂度练习题(没有明确要求,计算的时间复杂度就是最坏情况下) 3、空间复杂度 前言: 如何衡量一个算法的…

Java中多线程wait和notify的用法

目录 一、wait和notify/notifyAll的由来 二、wait()方法 三、notify方法 3.1 notify的作用 3.2 wait和notify的 相互转换代码图 3.3 notifyAll 四、为什么需要notify和wait都需要上锁? 五、wait和sleep的对比 前言:由于线程之间是抢占式执行的&a…

Linux常用命令——tftp命令

在线Linux命令查询工具(http://www.lzltool.com/LinuxCommand) tftp 在本机和tftp服务器之间使用TFTP协议传输文件 补充说明 tftp命令用在本机和tftp服务器之间使用TFTP协议传输文件。 TFTP是用来下载远程文件的最简单网络协议,它其于UDP协议而实现。嵌入式linu…

RTMP协议封装H264和H265协议详解

RTMP协议封装H264和H265协议详解 文章目录RTMP协议封装H264和H265协议详解1 RTMP和FLV2 RTMP协议封装H264视频流2.1 RTMP发送AVC sequence header2.2 RTMP发送AVCC视频帧数据‘3 RTMP协议封装H265视频流1 RTMP和FLV 有关RTMP和FLV格式详细介绍可查看如下文章: http…

2022 Moonbeam的点点滴滴离不开社区支持

Moonbeam成为首个上线波卡的平行链已经有一周年🎂啦,这是一段疯狂的旅程🏍。 为了纪念这一时刻,我们通过公开数据来回顾这一年的众多里程碑、更新和整体发生的一切。 让我们来回顾一下Moonbeam在2022年取得了哪些成就吧。 &…

GIS二维电子地图开发总结

二维平面地图,目前支撑设备渲染,真实场景,后期电子围栏,运动轨迹等业务需求做铺垫 一、所涉及的技术栈: 1.Openlayers,加载渲染地图 2.Geoserver 发布wms和wfs()服务 3.Arcgis,Arcmap,进行源文件…

3.1、Ubuntu20桌面版远程连接SSHMobaXterm远程连接编辑器

连接SSH 安装系统完成并登陆后,输入 修改源码地址 进入apt文件夹 cd /etc/apt 备份文件 cp sources.list sources.list.bak 修改源码地址 vi sources.list # See http://help.ubuntu.com/community/UpgradeNotes for how to upgrade to # newer versions of…

数据结构初级<排序>

本文已收录至《数据结构(C/C语言)》专栏! 作者:ARMCSKGT 你的阅读和理解将是我极大的动力! 目录 前言 排序的概念 常见排序简述 正文 直接插入排序 原理 代码实现 分析 希尔排序 原理 代码实现 分析 直接选择排序 原理 代码…

类加载的时机与过程

------ 摘自 周志明 《深入理解Java虚拟机》类加载的时机一个类型从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期将会经历加载(Loading)、验证(Verification)、准备(Preparati…

6、数组的常见运算

目录 一、数组的算术运算 二、数组的关系运算 三、数组的逻辑运算 一、数组的算术运算 (1)数组的加减运算:通过格式AB或A-B可实现数组的加减运算。但是运算规则要求数组A和B的维数相同。 示例1: A[1 2 3 4]B[2 4 6 8]C[1 1 …

三种简洁易行的方法解决基于Vue.js的组件通信

在总结Vue组件化编程的数据通信方面,看了网上的很多资料,都是讲父子组件的数据交互也就是参数传递,在组件的通信方面分几种情况,比如父子组件、非父子的兄弟组件、非父子的其他组件等等,这样看来,基于Vue.j…

STC15系列单片机EEPROM读写示例

STC15系列单片机EEPROM读写示例🌼STC15手册有关EEPROM描述 🌾STC15系列单片机内部集成了大容量的EEPROM,其与程序空间是分开的。利用ISP/IAP技术可将内部DataFlash当EEPROM,擦写次数在10万次以上。EEPROM可分为若干个扇区&#xf…

Android 蓝牙开发——蓝牙协议配置(七)

蓝牙主要分为两种模式,一种是媒体输出(Source)端,一种是媒体输入(Sink)端。也可以理解为服务端(Server)与客户端(Client)的关系。 蓝牙配置文件(B…

4-1指令系统-指令格式

文章目录一.指令的基本格式1.结构2.长度3.根据操作数地址码数目分类(1)零地址指令(2)一地址指令(3)二地址指令(4)三地址指令(5)四地址指令二.扩展操作码指令格…

Maven学习(二):Maven基础概念

Maven基础概念一、仓库二、坐标三、全局setting与用户setting区别一、仓库 仓库:用于存储资源,包含各种jar包;仓库分类: 本地仓库:自己电脑上的存储仓库,连接远程仓库获取资源;远程仓库&#x…

信息论复习—离散信道及其容量

目录 信道的简介: 信道的分类: 离散无记忆信道(DMC)模型: 转移概率: 离散无记忆信道的转移矩阵 输出仅与当前的输入有关: 后验概率: 离散无记忆信道的后验概率矩阵 &#xf…

spring-boot-starter-jdbc和mysql-connector-j依赖爆红的解决办法

spring-boot-starter-jdbc和mysql-connector-j依赖爆红的解决办法 目录spring-boot-starter-jdbc和mysql-connector-j依赖爆红的解决办法出现问题之前出现的问题:解决办法:方案一:第一种是继承 spring-boot-starter-parent 然后 依赖覆盖方案…

怎么用Python测网速?

“speedtest-cli” 是一个 Python 的第三方库,它可以用来在命令行中测试网络速度。它使用了 Speedtest.net 的服务器来进行测速,并可以提供下载和上传速度、延迟、丢包率等信息。使用这个库可以很方便地在终端中测试网络速度,而无需在浏览器中…