首页 > 编程语言 >C# 乐观锁和悲观锁

C# 乐观锁和悲观锁

时间:2023-09-15 20:11:47浏览次数:40  
标签:Thread C# 用户 乐观 线程 悲观 锁定 资源

1.概要

乐观锁(Optimistic Locking)

乐观锁的核心思想是假设在大多数情况下,资源不会发生冲突,因此允许多个用户或线程同时读取和修改资源。只有在真正发生冲突的时候才会进行冲突解决。

乐观锁的工作原理如下:

  1. 版本标识或时间戳:在资源中引入一个版本标识(Version)或时间戳(Timestamp)字段,用于记录资源的修改版本或修改时间。
  2. 读取资源:当一个用户或线程要读取资源时,会获取当前的版本号或时间戳,并将其保存在本地。
  3. 修改资源:当用户或线程要修改资源时,它会检查本地保存的版本号或时间戳与资源当前的版本号或时间戳是否匹配。如果匹配,表示没有其他用户或线程在修改这个资源,可以安全地进行修改。
  4. 冲突检测:如果本地的版本号与资源的版本号不匹配,表示资源已经被其他用户或线程修改,那么当前用户或线程需要处理冲突。通常的处理方式包括放弃修改、重新读取资源并重新应用修改,或者采用其他冲突解决策略。

乐观锁的优点是它不会在资源读取时进行锁定,允许多个用户并发地读取资源,提高了系统的并发性能。但是,如果冲突频繁发生,可能需要增加冲突解决的复杂性,以及重新读取和应用修改可能会导致性能损失。

乐观锁通常用于情况下,其中资源冲突的概率相对较低,例如读多写少的情况。另一方面,悲观锁则是一种更保守的并发控制机制,它会在读取资源时立即锁定,以确保不会发生冲突,但可能会降低系统的并发性能。选择哪种锁取决于应用程序的需求和性能要求。

悲观锁(Pessimistic Locking)

在多个用户或线程访问共享资源时采取一种悲观的态度,即假定在任何时刻都会发生冲突,因此在访问资源之前会将其锁定,以确保只有一个用户或线程能够访问资源,从而防止冲突和数据不一致性。

悲观锁的主要特点如下:

  1. 锁定资源:在用户或线程访问资源之前,悲观锁会锁定资源,阻止其他用户或线程对其进行读取或修改。这可以通过数据库中的行级锁、表级锁、文件锁或其他机制来实现,具体取决于应用程序和数据存储方式。
  2. 保守策略:悲观锁采用一种保守的策略,即假定并发访问会导致冲突,因此在访问资源时会进行锁定。这可以确保资源的一致性,但也可能导致性能问题,特别是在高并发环境下。
  3. 阻塞等待:如果一个用户或线程已经锁定了资源,其他试图访问相同资源的用户或线程可能需要等待,直到锁被释放为止。这可能导致竞争和延迟。
  4. 事务性:悲观锁通常与事务一起使用,以确保在事务中对资源进行读取和修改时不会被其他事务干扰。
  5. 适用场景:悲观锁通常用于资源冲突的概率较高的情况,或者当资源的一致性是至关重要的情况下。例如,在银行系统中,对于一个银行账户的并发访问,悲观锁可以确保不会出现超支或其他不一致的情况。

悲观锁是一种保守的并发控制机制,通过锁定资源以确保数据一致性,但可能导致性能问题和竞争。在选择锁定策略时,应根据应用程序的需求和性能要求来决定是否使用悲观锁。

差异

乐观锁和悲观锁是两种不同的并发控制机制,它们用于管理多个用户或线程同时访问共享资源的情况,但它们的工作方式有很大的区别。

态度差异:

  • 乐观锁:假设在大多数情况下不会发生冲突,允许多个用户或线程同时读取和修改资源,只有在发生冲突时才会进行处理。
  • 悲观锁:假设在任何时刻都会发生冲突,因此在访问资源之前会将其锁定,以确保只有一个用户或线程能够访问资源。

锁定时机:

  • 乐观锁:在资源访问时不进行锁定,只在提交修改时才检查冲突。
  • 悲观锁:在访问资源之前就会进行锁定,以防止其他用户或线程同时访问。

性能影响:

  • 乐观锁:通常具有较高的并发性能,因为它允许多个用户或线程同时读取资源,只在冲突发生时才会引入竞争和延迟。
  • 悲观锁:可能导致性能问题,因为它在访问资源时会锁定,其他用户或线程需要等待锁的释放,可能会引入竞争和延迟。

冲突解决方式:

  • 乐观锁:发生冲突时,通常需要重新读取资源并重新应用修改,或者采用其他冲突解决策略,如版本号比对。
  • 悲观锁:在资源访问之前就会锁定资源,因此冲突的概率较低。冲突通常通过等待其他锁定的释放来解决。

适用场景:

  • 乐观锁:通常适用于资源冲突的概率较低的情况,例如读多写少的情况。
  • 悲观锁:通常适用于资源冲突的概率较高,或者当资源的一致性是至关重要的情况下。

乐观锁和悲观锁适用于不同的应用场景。选择哪种策略取决于应用程序的需求、性能要求以及对一致性的要求。乐观锁通常用于提高并发性能,而悲观锁用于确保数据的强一致性。

2.案例示例

如何实现一个乐观锁?

以下代码仅供参考。

class Program
{
    private static int sharedValue = 0;
    private static int version = 0;
​
    static void Main(string[] args)
    {
        // 模拟两个线程尝试同时更新共享值
        Task t1 = Task.Run(() => UpdateSharedValue(1));
        Task t2 = Task.Run(() => UpdateSharedValue(2));
​
        Task.WaitAll(t1, t2);
​
        Console.WriteLine($"Final Shared Value: {sharedValue}");
    }
​
    static void UpdateSharedValue(int id)
    {
        int localVersion;
​
        localVersion = Interlocked.CompareExchange(ref version, 0, 0); // 获取当前版本号
​
        Console.WriteLine($"Thread {id}: Current Version: {localVersion}");
​
        // 模拟一些计算或操作
        Thread.Sleep(100);
​
        // 检查版本是否仍然匹配
        if (localVersion == version)
        {
            Interlocked.Increment(ref sharedValue); // 更新共享值
            Interlocked.Increment(ref version); // 增加版本号
            Console.WriteLine($"Thread {id}: Value Updated. New Value: {sharedValue}, New Version: {version}");
        }
        else
        {
            Console.WriteLine($"Thread {id}: Update Failed due to version mismatch.");
        }
    }
}

如何实现一个悲观锁?

这里使用lock语句来锁定共享资源,以确保在一个线程访问资源时其他线程无法同时访问。

class Program
{
    static object resourceLock = new object(); // 定义一个锁对象,用于锁定共享资源
​
    static int sharedResource = 0; // 共享资源
​
    static void Main(string[] args)
    {
        Console.WriteLine("Starting updates...");
​
        Thread t1 = new Thread(UpdateResource);
        Thread t2 = new Thread(UpdateResource);
​
        t1.Start("Thread 1");
        t2.Start("Thread 2");
​
        t1.Join();
        t2.Join();
​
        Console.WriteLine("Updates completed.");
        Console.WriteLine($"Final shared resource value: {sharedResource}");
    }
​
    static void UpdateResource(object threadName)
    {
        Console.WriteLine($"{threadName} is updating the resource.");
​
        // 使用锁来锁定共享资源
        lock (resourceLock)
        {
            // 模拟一些工作
            Thread.Sleep(1000);
​
            // 更新共享资源
            sharedResource++;
            Console.WriteLine($"{threadName} updated the resource to {sharedResource}");
        }
​
        Console.WriteLine($"{threadName} finished updating the resource.");
    }
}

示例中使用了resourceLock的对象来锁定共享资源sharedResource。两个线程(Thread 1 和 Thread 2)尝试同时更新共享资源,但只有一个线程可以在某一时刻获得锁,进而访问和更新共享资源。其他线程必须等待锁的释放。

转载C#中的悲观锁和乐观锁 - 知乎 (zhihu.com)

标签:Thread,C#,用户,乐观,线程,悲观,锁定,资源
From: https://www.cnblogs.com/SmallCarp/p/17705849.html

相关文章

  • Python集成开发环境IDE-Pycharm 2023 win+mac版
    PyCharm2023是一种流行的集成开发环境(IDE),专门为Python开发人员设计.→→↓↓载Pycharm2023mac/winPyCharm2023版提供了强大的代码编辑器,支持智能代码完成、代码分析、代码重构等功能。它还可以自动检测错误并提供修复建议。PyCharm的调试器非常强大,可以帮助开发人员诊断和......
  • debia12报错 perl: warning: Setting locale failed
    目录前奏情景复现官方解释解决以防走丢前奏该文档是在Debian12上遇到的问题,特别做个记录情景复现perl:warning:Settinglocalefailed.perl:warning:Pleasecheckthatyourlocalesettings:LANGUAGE="en_US:en",LC_ALL=(unset),LC_CTYPE="zh_CN.UTF-8",LANG......
  • Elasticsearch 命令行初识
    完整教程:https://www.cnblogs.com/shaner/p/5661071.htmlhttps://zhuanlan.zhihu.com/p/449555826http://192.168.1.10:9201/_cat/health?vhttp://192.168.1.10:9201/_cathttp://192.168.1.10:9201/_cat/indices?vES服务器命令行:----服务器命令行删除大索引curl-XDELETEhttp:......
  • Rockchip RK3399 - USB触摸屏接口驱动
    一、触摸屏接口分类触摸屏主要包括电阻触摸屏和电容触摸屏,这个我们在《linux驱动移植-LCD触摸屏设备驱动》中已经详细介绍了,这里不再重复介绍。在《linux驱动移植-LCD触摸屏设备驱动》这篇文章中我们介绍了SoCS3C2440触摸屏驱动的实现,对于S3C2440来说,其只支持四线电阻触摸屏。......
  • UEditor的word图片转存-Electron篇
    electorn是用nodejs写桌面端应用,详细的可从官方文档上获得:Electron文档完整的应用地址为:Word-Image-Handler在这里需要实现如下几点:1.在子进程中跑nodejs服务2.实现自动更新3.可唤起Nodejs服务因为,我这里需要跑一个nodejs应用,跑在子进程中,直接上一个大神的项目地址:Electro......
  • halcon AI读取onnx模型并推理
    *程序功能:读取onnx模型并推理dev_update_off()dev_close_window()read_dl_model('squeezenet.onnx',DLModelHandle)set_dl_model_param(DLModelHandle,'type','classification')get_dl_model_param(DLModelHandle,'image_dimensions',......
  • 无涯教程-JavaScript - LOOKUP函数
    描述需要查看单个行或一列并从第二行或第二列的同一位置查找值时,请使用LOOKUP函数。使用"查找"功能搜索一行或一列。使用VLOOKUP函数可搜索一行或一列,或搜索多行和多列(如表)。它是LOOKUP的改进版本。有两种使用LOOKUP的方法-矢量形式−UsethisformofLOOKUPtosearc......
  • WebRTC C++ RTP over TCP配置
    前言RTPoverTCP这种情况,一般是WebRTCP2P打洞失败,才会选择WebRTC默认使用UDP传输,但是也可以通过TCP传输。使用TCP传输,需要服务器中转,turnserver,licode,janus之类的服务器解决方案搭建coTurn中转服务器https://blog.51cto.com/fengyuzaitu/7265986C++修改代码url后面必须指定?t......
  • CF662B Graph Coloring
    很一眼的题考虑枚举最后所有边的颜色,然后每个点是否变化可以用一个bool变量表示,就是个很典的2-SAT问题,根据当前边和目标的颜色相同与否连边即可但这题的难点在于要找一个操作次数最少的方案,乍一看很难搞但如果你对图论和2-SAT那一套理解比较深的话就很容易发现,这道题中所有边都......
  • Codeforces Round 764 (Div. 3) B. Make AP
    有三个正整数\(a,b,c\)。需要执行以下操作严格一次:选择任意一个正整数\(m\)并让严格一个\(a,b,c\)之一乘以\(m\)。但不能改变他们的顺序。回答是否可以经过一次操作后使\(a,b,c\)变为等差。分类讨论题:三种情况满足一种即可。(已知\(a,b,c\geq1\))\(ma......