首页 > 编程语言 >C# 多线程锁

C# 多线程锁

时间:2024-03-21 11:58:05浏览次数:27  
标签:释放 同步 C# lock 获取 线程 Mutex 多线程

C# 多线程锁

分类

  1. lock (Monitor):
  • lock 是 C# 中的关键字,它实际上是 Monitor 类的一个简化版本的语法糖。
  • 使用方式:lock (obj) { // 代码块 },其中 obj 是一个对象引用,所有线程都试图获取该对象的互斥锁。
  • 功能:确保同一时间只有一个线程可以进入受保护的代码块。
  • 应用场景:适用于大多数简单的互斥需求,尤其是在对某个数据结构或对象进行修改时,需要保证操作的原子性和一致性。
  1. Monitor:
  • Monitor 类提供了比 lock 更底层的方法,如 Monitor.Enter(obj) 和 Monitor.Exit(obj),以及 Monitor.TryEnter(obj, timeout),Wait,Pulse和PulseAll 等高级功能。
  • 包括了锁的获取和释放,并支持条件变量,允许线程等待特定条件变为真后继续执行。
  • 应用场景:与 lock 类似,但在需要更精细控制的情况下,比如手动控制锁的获取和释放,或者在等待条件满足时才继续执行。
  1. Mutex:
  • Mutex 是操作系统级别的互斥体,跨进程有效。
  • 可以在多个进程间同步访问资源。
  • 使用 WaitOne() 和 ReleaseMutex() 方法来获取和释放锁。
  • 应用场景:跨进程的同步,尤其是在多个应用程序之间需要共享资源或协调操作时。
  1. Semaphore:
  • 信号量允许一定数量的线程同时访问特定资源。
  • 通过控制可同时进入关键区域的线程数来管理资源池。
  • 应用场景:当有多个类似资源但不希望所有线程同时访问时,如数据库连接池等。
  • 轻量级版为SemaphoreSlim,支持异步。
  1. ReaderWriterLockSlim:
  • 提供了读写锁的功能,允许多个读取者共享锁,但在写入者请求时阻止所有读取和写入。
  • 提高了读取密集型场景下的并发性能。
  • 使用 EnterReadLock(), ExitReadLock(), EnterWriteLock(), ExitWriteLock() 等方法。
  • 应用场景:适用于读取频繁而写入较少的数据结构,例如缓存或配置信息。
  • 重量级版为ReaderWriterLock。
  1. SpinLock:
  • 自旋锁是一种低级别的锁机制,在尝试获取锁时,线程不会立即挂起,而是循环检查锁的状态(即“自旋”)直至锁可用。
  • 适合于锁持有时间极短且线程切换开销较大的场合。
  • 使用 SpinLock.SpinUntil() 或 TryEnter() 方法。
  • 应用场景:在预期锁很快会被释放的情况下,特别是在高性能计算环境中。
  1. volatile 关键字:
  • 不是严格意义上的锁,但有助于确保多线程环境下对变量的访问可见性。
  • 当标记一个字段为 volatile 后,编译器和运行时会确保任何对该字段的读/写操作都不会被优化掉,并且会从主内存而非CPU缓存中读取值。
  • 应用场景:主要用于确保线程间的内存可见性,但它并不能保证原子操作。

区别

  1. Semaphore vs SemaphoreSlim
  • Semaphore和SemaphoreSlim都是用于限制同时访问某一资源或资源池的线程数量的同步原语。它们都有一个计数器,当线程请求访问资源时,计数器会减少;当线程释放资源时,计数器会增加。当计数器为零时,新的线程将被阻塞,直到其他线程释放资源。
  • Semaphore是一个重量级的同步原语,可以在不同的进程之间使用。它涉及到系统级的操作,所以使用Semaphore的开销比使用SemaphoreSlim更大。
  • SemaphoreSlim是一个轻量级的同步原语,只能在同一进程的不同线程之间使用。它主要用于在任务和线程之间进行同步,特别是在并行程序中。
  1. ReaderWriterLock vs ReaderWriterLockSlim
  • ReaderWriterLock和ReaderWriterLockSlim都是用于控制对资源的读写访问的同步原语。它们允许多个线程同时进行读取操作,但一次只允许一个线程进行写入操作。
  • ReaderWriterLock是一个早期的.NET同步原语。尽管它在功能上很强大,但在性能上存在一些问题。特别是在高竞争的情况下,ReaderWriterLock可能会导致线程饥饿。
  • ReaderWriterLockSlim是.NET 3.5引入的一个新的同步原语,用于解决ReaderWriterLock的一些性能问题。ReaderWriterLockSlim在设计时考虑了性能,因此在许多情况下,它比ReaderWriterLock更快。然而,ReaderWriterLockSlim的API更复杂,需要更谨慎的使用。
  1. lock vs Mutex
  • 作用范围lock只能在同一进程中的不同线程之间进行同步,而Mutex可以在不同的进程之间进行同步。这意味着如果你需要在多个应用程序之间同步访问某个资源,你应该使用Mutex
  • 性能:由于Mutex可以跨进程使用,因此它需要进行更多的系统级操作,这使得Mutex的性能开销比lock更大。如果你只需要在同一进程的线程之间进行同步,通常推荐使用lock,因为它的性能更好。
  • 所有权Mutex有所有权的概念,即只有创建或获取Mutex的线程才能释放它。而lock则没有这个限制,任何线程都可以释放lock
  • 异常安全:在lock的代码块中,如果发生异常,lock会自动释放。但是,如果你使用Mutex,你需要在finally块中显式释放它,以确保在发生异常时Mutex被正确释放。
  • 重入机制:lock(Monitor)和Mutex都允许重入,但二者有些区别:Mutex对于每次成功的WaitOne()调用(即获取锁),都需要对应一次ReleaseMutex()调用(即释放锁)。这意味着如果一个线程已经拥有了Mutex,然后再次尝试获取同一个Mutex,那么这个线程可以成功获取,但是这个线程需要调用两次ReleaseMutex()来完全释放Mutex。而lock(Monitor)不需要对应的多次Exit()调用。也就是说,无论Enter()调用了多少次,只需要一次Exit()就可以完全释放锁。

注意: 跨进程锁,是指在同一操作系统下,比如一个应用程序的多开。

使用场景

  1. Mutex
  • 跨进程实现

    Mutex(互斥锁)是一种同步原语,可以用于跨进程同步。在C#中,你可以通过命名Mutex来实现跨进程同步。

当你创建一个Mutex时,你可以给它一个唯一的名称。然后,其他的进程可以通过这个名称来打开并使用同一个Mutex。这样,不同的进程就可以通过这个共享的Mutex来同步对共享资源的访问。

以下是一个例子:

// 创建一个名为"MyMutex"的Mutex
bool createdNew;
Mutex mutex = new Mutex(true, "MyMutex", out createdNew);

if (createdNew)
{
    Console.WriteLine("This process created the mutex.");
}
else
{
    Console.WriteLine("This process opened an existing mutex.");
}

// 使用Mutex保护的代码区域
try
{
    // 获取Mutex
    mutex.WaitOne();

    // 在这里访问共享资源
}
finally
{
    // 释放Mutex
    mutex.ReleaseMutex();
}

在这个例子中,如果"MyMutex"已经存在,那么新的进程将打开已经存在的Mutex,而不是创建一个新的。然后,这个进程可以通过WaitOneReleaseMutex方法来获取和释放Mutex,从而实现对共享资源的同步访问。

需要注意的是,Mutex是一个重量级的同步原语,因为它涉及到系统级的操作。因此,如果你只需要在同一进程的线程之间进行同步,你应该使用更轻量级的同步原语,如lockMonitor

  • Mutex递归和非递归

非递归Mutex(Non-Recursive Mutex): 假设有一个非递归Mutex M,线程A先获取了M,此时其他线程无法获取M。如果线程A在未释放M之前再次尝试获取M,非递归Mutex会认为这是一个错误的行为(即发生了死锁),因为线程A已经持有这个锁,再次请求会导致线程A自己被阻塞。

using System.Threading;

class NonRecursiveMutexExample
{
    Mutex nonRecursiveMutex = new Mutex(false, "NonRecursiveMutex");

    public void SomeMethod()
    {
        nonRecursiveMutex.WaitOne(); // 线程A获取了mutex
        
        // ... 执行一些操作 ...
        
        // 如果在这儿再次尝试获取mutex
        nonRecursiveMutex.WaitOne(); // 此时线程A会被阻塞,因为它已经在等待自己持有的锁
    }
}

递归Mutex(Recursive Mutex): 对于递归Mutex,线程在已经持有锁的情况下可以再次获取该锁,并跟踪锁的获取次数。只有在释放相同次数后,锁才会真正被释放给其他线程。

using System.Threading;

class RecursiveMutexExample
{
    Mutex recursiveMutex = new Mutex(true, "RecursiveMutex"); // 注意这里的构造函数参数true表示创建递归Mutex

    public void SomeMethod()
    {
        recursiveMutex.WaitOne(); // 线程A获取了mutex
        
        // ... 执行一些操作 ...

        // 再次尝试获取mutex
        recursiveMutex.WaitOne(); // 由于是递归Mutex,线程A仍能获取,内部计数器加1
        // ... 继续执行一些依赖于锁的操作 ...

        // 要释放锁,需要调用ReleaseMutex对应次数
        recursiveMutex.ReleaseMutex(); // 第一次释放,计数器减1,锁仍然保持
        recursiveMutex.ReleaseMutex(); // 第二次释放,计数器减至0,锁真正被释放
    }
}

标签:释放,同步,C#,lock,获取,线程,Mutex,多线程
From: https://www.cnblogs.com/Nine4Cool/p/18086756

相关文章

  • C# 使用HttpListener时候异常(此平台不支持此操作:System.PlatformNotSupportedExceptio
    ​ C#使用HttpListener时候异常(此平台不支持此操作:System.PlatformNotSupportedException)代码:HttpListenerlistener=newHttpListener();错误:System.PlatformNotSupportedException:OperationisnotsupportedonthisplatformInSystem.Net.HttpListener..ctor()......
  • 如何下载和安装 macOS
    在兼容的Mac电脑上下载并安装最新或以前版本的Mac操作系统。为了保持电脑的安全性、稳定性和兼容性,Apple建议使用与你的Mac兼容的最新版macOS。macOS更新和升级还包含最新的功能和内建App(如Safari浏览器)。使用“软件更新”使用“macOS恢复”使用AppStore......
  • 创建PV、PVC
    apiVersion:v1kind:PersistentVolumemetadata:name:pv-v1labels:app:v1spec:nfs:server:10.16.17.57#NFS服务器地址path:/data/volumes/v1#NFS路径accessModes:["ReadWriteOnce"]......
  • 更智能的广告素材生成!看A/B测试如何驱动AIGC素材调优
    更多技术交流、求职机会,欢迎关注字节跳动数据平台微信公众号,回复【1】进入官方交流群前言:AIGC大爆发,引发广告营销行业变革ChatGPT等AI产品引发的AIGC大爆发引起了各行业的震动,其中以图片生成甚至视频生成技术的效果和速度最为令人震撼。也正因如此,AIGC的爆发对一直以创意为核......
  • LeetCode 2265. Count Nodes Equal to Average of Subtree
    原题链接在这里:https://leetcode.com/problems/count-nodes-equal-to-average-of-subtree/description/题目:Giventhe root ofabinarytree,return thenumberofnodeswherethevalueofthenodeisequaltothe average ofthevaluesinits subtree.Note:Th......
  • SPPSVC.EXE(Software Protection Platform Service)是Windows操作系统中的一个进程,起源
    ‪C:\Windows\System32\sppsvc.exeSPPSVC.EXE(SoftwareProtectionPlatformService)是Windows操作系统中的一个进程,起源于微软公司为了保护其软件版权而开发的软件保护服务。这个服务主要负责验证Windows的许可证信息、管理软件激活状态以及执行与软件许可证相关的任务。SPPSVC.E......
  • C++ RTTI
    1.背景RTTI的英文全称是"RuntimeTypeIdentification",中文称为"运行时类型识别",它指的是程序在运行的时候才确定需要用到的对象是什么类型的。用于在运行时(而不是编译时)获取有关对象的信息。在C++中,由于存在多态行为,基类指针或者引用指向一个派生类,而其指向的真正类型,在编译阶......
  • docker容器卷是什么
    数据卷的坑: 解释原因:容器数据卷,完成数据持久化重要资料的备份(backup):   容器卷是什么? ps:docker不会再容器删除时删除其挂载的数据卷。容器卷能干吗? 容器卷是事实的,比下面这种手动在容器内拷贝文件到主机上更加方便: ......
  • CSS样式表 样式优先级 选择器以及选择器的权重优先级
     CSS组成    css由选择符和声明组成,声明又分为属性和属性值    属性必须放在花括号里面,属性与属性值必用冒号连接    每条声明用分号结束    当一个属性有多个属性值的时候,属性值与属性值部分先后顺序,用空格隔开    在书写样......
  • C语言之打鱼晒网问题
    目录一简介二代码实现步骤一:判断输入日期步骤二:计算总天数步骤三:判断行为模式三时空复杂度一简介打鱼晒网问题是一个经典的编程题目,源自中国的俗语“三天打鱼两天晒网”,意思是周期性地工作和休息。在C语言中实现这一问题的程序设计目标是:当给定一个日期后,计算......