.net 中并发控制

代码中经常使用多线程的方式来提高应用程序的性能,但当多个线程或进程并发执行同一段代码时,可能会导致竞争条件,死锁,数据不一致等线程安全问题;因此需要使用线程安全的代码或数据结构来避免。.net core 中提供了多种方式来控制并发访问共享资源,实现线程安全。

原子操作

System.Threading中的Interlocked类提供了用于进行原子操作的静态方法,用于确保对共享变量的增减操作是线程安全的,避免了使用锁机制带来的额外的性能开销,可以在高并发的场景下可以减少锁的竞争。

int counter = 0;

public void IncrementCounter()
{
    Interlocked.Increment(ref counter); // 线程安全的原子操作
}

锁机制

锁机制是常用的并发控制机制,通过锁定资源,确保同一时刻只有一个线程可以访问特定的代码块或数据结构。.net core 在System.Threading命名空间提供了多种锁机制来实现并发控制。

lock语句(Monitor)

lock语句是调用以 try/finally 块封装的 Monitor.Enter() 和 Monitor.Exit() 方法的语法糖,两者都用于线程同步,确保同一时刻只有一个线程能够进入被锁保护的代码块,其他并发访问的线程会被阻塞,直到当前线程释放锁。

private static readonly object _lock = new object();

public void Increment()
{
    lock (_lock)
    {
        counter++; // 只有一个线程能进入此代码块
    }
}

//monitor语句
public void Increment() 
{ 
	// 使用 Monitor.Enter 和 Monitor.Exit  
	for (int i = 0; i < 1000; i++) 
	{ 
		Monitor.Enter(lockObject); // 锁定对象,进入临界区 
		try 
		{ 
			counter++; 
		} 
		finally 
		{ 
			Monitor.Exit(lockObject); // 解锁,使其他线程可以进入临界区
		} 
	} 
}

Mutex

Mutex可以进行跨进程的同步机制,确保同一时刻只有一个进程能访问特定资源,适用于多个进程间的同步。

Mutex mutex = new Mutex(false, "MyMutex");
public void SomeMethod()
{
    mutex.WaitOne();
    try
    {
        // 执行需要同步的操作
    }
    finally
    {
        mutex.ReleaseMutex();
    }
}

Semaphore 和SemaphoreSlim

SemaphoreSemaphoreSlim允许最多N个线程同时访问共享资源,超出数量的线程会被阻塞,直到有其他线程释放资源,适用于连接池,数据库池等需要限制并发访问数量的场景。
SemaphoreSlime: 轻量级的实现,专门设计用来提高线程同步的性能,避免了较重的操作系统调用,因此可以用于单一进程内线程同步。
Semaphore: 通过操作系统的内核信号量支持进程间同步,可以在不同的进程中共享信号量,适用于需要跨进程控制并发的场景。

SemaphoreSlim semaphore = new SemaphoreSlim(3, 3);

public async Task SomeMethodAsync()
{
    await semaphore.WaitAsync();
    try
    {
        // 执行同步操作
    }
    finally
    {
        semaphore.Release();
    }
}

SpinLock

SinLock是一种轻量级的锁,通过自旋(不断循环检查锁的状态)来尝试获取锁。自旋过程中,线程不会被挂起,不涉及操作系统的上下文切换,避免了线程挂起和调度的高开销,适用于锁竞争时间比较短的情况。

SpinLock spinLock = new SpinLock();
bool lockTaken = false;

public void SomeMethod()
{
    spinLock.Enter(ref lockTaken);  // 获取锁
    try
    {
        // 执行同步操作
    }
    finally
    {
        if (lockTaken)
            spinLock.Exit();  // 释放锁
    }
}

ReaderWriterLock和ReaderWriterLockSlim

两种锁允许多个线程同时进行读取,但在写入时,同一时间只有一个线程可以进行,适用于读多写少的场景,如缓存、配置文件等。
两种锁的功能相似,官方文档推荐使用ReaderWriterLockSlim,因为其简化了递归规则以及锁状态的升级降级,并且性能显著优于ReaderWriterLock,具体区别见官方文档。

ReaderWriterLockSlim lockSlim = new ReaderWriterLockSlim();

public void ReadData()
{
    lockSlim.EnterReadLock();
    try
    {
        // 执行读取操作
    }
    finally
    {
        lockSlim.ExitReadLock();
    }
}

public void WriteData()
{
    lockSlim.EnterWriteLock();
    try
    {
        // 执行写入操作
    }
    finally
    {
        lockSlim.ExitWriteLock();
    }
}

线程安全的数据结构

.net core 线程安全的数据结构位于System.Collections.Concurrent命名空间,包括ConcurrentDictionary,ConcurrentQueue,ConcurrentBag等,提供了线程并发访问控制,避免了直接使用显式锁来保护集合,具体使用方式类似对应的非线程安全的数据结构。

Immutable集合

System.Collections.Immutable命名空间中提供了不可变集合,Immutable集合对象一经创建后不可修改,适用于需要共享和并发访问的数据结构,并且不需要修改数据。包括ImmutableList<T>,ImmutableQueue<T>,ImmutabelDictionary等。

参考内容

Introduction To Locking And Concurrency Control in .NET 6
What is Locking and How to Use a Locking Mechanism in C#
System.Threading
System.Collections.Immutable
System.Collections.Concurrent
ChatGPT

posted @   岛dao  阅读(30)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 记一次.NET内存居高不下排查解决与启示
点击右上角即可分享
微信分享提示