当前位置: 首页 > news >正文

C# 多线程编程:线程锁与无锁并发

文章目录

  • 前言
  • 一、锁的基本概念
    • 1.1 什么是锁?
    • 1.2 为什么需要锁?
    • 1.3 锁的作用原理
  • 二、线程锁的类型
    • 2.1 自旋锁(Spin Lock)
    • 2.2 互斥锁(Mutex)
    • 2.3 混合锁(Hybrid Lock)
    • 2.4 读写锁(Read-Write Lock)
  • 三、锁的实现方式
    • 3.1 Monitor(互斥体)
    • 3.2 Mutex(互斥体)
    • 3.3 Semaphore(信号量)
    • 3.4 ReaderWriterLock(读写锁)
  • 四、无锁并发编程
    • 4.1 无锁并发编程的概念
    • 4.2 无锁算法
      • 4.2.1 CAS(Compare And Swap)
      • 4.2.2 Volatile 关键字
    • 4.3 无锁并发编程的优势
    • 4.4 无锁并发编程的局限性
  • 五、并发集合类
    • 5.1 ConcurrentBag
    • 5.2 ConcurrentDictionary
    • 5.3 ConcurrentQueue
    • 5.4 ConcurrentStack
  • 六、经典并发同步问题
    • 6.1 生产者-消费者问题(Producer-Consumer Problem)
      • 6.1.1 使用 `Monitor` 类实现生产者-消费者问题
      • 6.1.2 使用 `Semaphore` 类实现生产者-消费者问题
      • 6.1.3 使用 `BlockingCollection` 类实现生产者-消费者问题
    • 6.2 读者-写者问题(Reader-Writer Problem)
      • 6.2.1 使用 `ReaderWriterLockSlim` 类实现读者-写者问题
      • 6.2.2 使用 `SemaphoreSlim` 类实现读者-写者问题
      • 6.2.3 使用 `Monitor` 类实现读者-写者问题
    • 6.3 哲学家就餐问题(Dining Philosophers Problem)
      • 6.3.1 使用`Semaphore`实现哲学家就餐问题
      • 6.3.2 使用`Mutex`实现哲学家就餐问题
      • 6.3.3 使用`Monitor`实现哲学家就餐问题
  • 总结


前言

多线程编程在现代软件开发中至关重要。本文将讨论 C# 中的多线程技术,重点介绍锁的概念,线程锁与无锁并发。通过学习本篇博文,我们将学会如何正确处理并发问题,提高程序的性能和稳定性。


一、锁的基本概念

在多线程编程中,掌握锁的概念至关重要。本节将介绍什么是锁,为什么我们需要锁以及锁的作用原理。

1.1 什么是锁?

锁是一种同步机制,用于控制多个线程对共享资源的访问。当一个线程获得了锁时,其他线程将被阻塞,直到该线程释放了锁。

1.2 为什么需要锁?

在并发编程中,多个线程同时访问共享资源可能导致数据竞争和不确定的行为。锁可以确保在任意时刻只有一个线程可以访问共享资源,从而避免竞态条件和数据不一致性问题。

1.3 锁的作用原理

锁的作用原理通常涉及到内部的互斥机制。当一个线程获得锁时,它会将锁标记为已被占用,其他线程尝试获取该锁时会被阻塞,直到持有锁的线程释放锁。这种互斥机制可以通过不同的算法和数据结构来实现,如互斥量、自旋锁等。

理解锁的概念是进行多线程编程的基础,它为我们提供了一种可靠的方式来保护共享资源,确保线程安全和程序的正确性。在接下来的章节中,我们将深入探讨不同类型的锁以及它们在 C# 多线程编程中的应用。

二、线程锁的类型

在多线程编程中,锁的实现通常基于互斥机制,确保在任意时刻只有一个线程可以访问共享资源。本节将介绍几种常见的锁类型,包括自旋锁、互斥锁、混合锁和读写锁。

2.1 自旋锁(Spin Lock)

  • 自旋锁是一种基于忙等待的锁,当线程尝试获取锁时,如果发现锁已被其他线程占用,它会循环(自旋)等待,不断地检查锁是否被释放。
  • 自旋锁适用于锁的占用时间短、线程并发度高的情况,因为它避免了线程在等待锁时进入内核态造成的性能损失。
  • 但自旋锁可能会导致线程空转消耗 CPU 资源,因此不适合在锁被占用时间较长或竞争激烈的情况下使用。

2.2 互斥锁(Mutex)

  • 互斥锁是一种阻塞式锁,它通过操作系统提供的原语实现,当线程尝试获取锁时,如果发现锁已被其他线程占用,它会被阻塞,直到锁被释放。
  • 互斥锁适用于锁的占用时间长、线程竞争激烈的情况,因为它可以将等待锁的线程置于休眠状态,避免空转浪费 CPU 资源。
  • 但互斥锁由于涉及系统调用,因此会产生较大的开销,尤其在高并发情况下可能成为性能瓶颈。

2.3 混合锁(Hybrid Lock)

  • 混合锁是结合了自旋锁和互斥锁的优点,根据锁的占用情况动态选择使用自旋等待还是阻塞等待。
  • 在锁的竞争不激烈时,混合锁会采用自旋等待的方式,避免线程进入内核态;而在锁的竞争激烈时,会转为阻塞等待,以减少空转和CPU资源的浪费。
  • 混合锁的实现较为复杂,需要根据具体的场景进行调优,以达到最佳的性能和资源利用率。

2.4 读写锁(Read-Write Lock)

  • 读写锁允许多个线程同时对共享资源进行读取操作,但在进行写入操作时需要互斥。
  • 读写锁适用于读操作远远多于写操作的场景,可以提高程序的并发性能。
  • 读写锁通常包含一个写锁和多个读锁,当写锁被占用时,所有的读锁和写锁都会被阻塞;而当读锁被占用时,其他的读锁仍然可以被获取,但写锁会被阻塞。

三、锁的实现方式

下面是几种常见的锁类型:

3.1 Monitor(互斥体)

Monitor 是 C# 中最基本的锁机制之一,它使用 lock 关键字来实现。lock 关键字在进入代码块时获取锁,在退出代码块时释放锁。这确保了在同一时刻只有一个线程可以执行 lock 块中的代码。

using System;
using System.Threading;class Program
{private static object _lock = new object();static void Main(string[] args){// 启动两个线程访问临界区Thread thread1 = new Thread(EnterCriticalSection);Thread thread2 = new Thread(EnterCriticalSection);thread1.Start();thread2.Start();}static void EnterCriticalSection(){// 进入临界区Monitor.Enter(_lock);try{// 在临界区内操作共享资源Console.WriteLine($"Thread {Thread.CurrentThread.ManagedThreadId} entered critical section.");Thread.Sleep(2000);}finally{// 退出临界区Monitor.Exit(_lock);Console.WriteLine($"Thread {Thread.CurrentThread.ManagedThreadId} exited critical section.");}}
}

另一种写法:

object lockObj = new object();
lock (lockObj)
{// 执行需要同步的代码
}

3.2 Mutex(互斥体)

Mutex 是一种操作系统级别的同步原语,与 Monitor 不同,Mutex 可以在进程间共享。Mutex 是一个系统对象,它可以在全局范围内唯一标识一个锁。使用 Mutex 需要在代码中声明一个 Mutex 对象,然后通过 WaitOne 和 ReleaseMutex 方法来获取和释放锁。

using System;
using System.Threading;class Program
{private static Mutex _mutex = new Mutex();static void Main(string[] args){// 启动两个线程访问临界区Thread thread1 = new Thread(EnterCriticalSection);Thread thread2 = new Thread(EnterCriticalSection);thread1.Start();thread2.Start();}static void EnterCriticalSection(){// 等待获取 Mutex_mutex.WaitOne();try{// 在临界区内操作共享资源Console.WriteLine($"Thread {Thread.CurrentThread.ManagedThreadId} entered critical section.");Thread.Sleep(2000);}finally{// 释放 Mutex_mutex.ReleaseMutex();Console.WriteLine($"Thread {Thread.CurrentThread.ManagedThreadId} exited critical section.");}}
}

3.3 Semaphore(信号量)

Semaphore 是一种允许多个线程同时访问共享资源的同步原语。它通过一个计数器来控制同时访问资源的线程数量。Semaphore 构造函数需要指定初始的计数器值和最大的计数器值。通过 WaitOne 和 Release 方法来获取和释放信号量。

using System;
using System.Threading;class Program
{private static Semaphore _semaphore = new Semaphore(2, 2); // 允许最多两个线程同时访问static void Main(string[] args){// 启动五个线程访问临界区for (int i = 0; i < 5; i++){Thread thread = new Thread(EnterCriticalSection);thread.Start(i);}}static void EnterCriticalSection(object threadId){// 等待获取 Semaphore_semaphore.WaitOne();try{// 在临界区内操作共享资源Console.WriteLine($"Thread {threadId} entered critical section.");Thread.Sleep(2000);}finally{// 释放 Semaphore_semaphore.Release();Console.WriteLine($"Thread {threadId} exited critical section.");}}
}

3.4 ReaderWriterLock(读写锁)

ReaderWriterLock 是一种特殊的锁机制,它允许多个线程同时读取共享资源,但在写入资源时需要互斥。这种锁适用于读操作远远多于写操作的场景,可以提高性能。

using System;
using System.Threading;class Program
{private static ReaderWriterLockSlim _rwLock = new ReaderWriterLockSlim();static void Main(string[] args){// 启动五个读线程和一个写线程访问共享资源for (int i = 0; i < 5; i++){Thread readerThread = new Thread(ReadSharedResource);readerThread.Start(i);}Thread writerThread = new Thread(WriteSharedResource);writerThread.Start();}static void ReadSharedResource(object threadId){_rwLock.EnterReadLock();try{// 读取共享资源Console.WriteLine($"Reader {threadId} read shared resource.");Thread.Sleep(2000);}finally{_rwLock.ExitReadLock();}}static void WriteSharedResource(){_rwLock.EnterWriteLock();try{// 写入共享资源Console.WriteLine("Writer wrote shared resource.");Thread.Sleep(1000);}finally{_rwLock.ExitWriteLock();}}
}

四、无锁并发编程

在多线程编程中,除了使用锁机制来保护共享资源外,还可以通过无锁并发编程来实现并发控制。本章将介绍无锁并发编程的概念、优势以及常见的无锁算法。

4.1 无锁并发编程的概念

无锁并发编程是一种基于原子操作的并发控制方式,它不需要使用传统的锁机制来保护共享资源,而是通过原子性操作来确保线程安全。无锁并发编程通常比锁机制具有更低的开销和更高的性能。

4.2 无锁算法

4.2.1 CAS(Compare And Swap)

CAS 是一种原子操作,通常由处理器提供支持。它涉及三个操作数:内存位置(通常是一个地址)、旧的预期值和新的值。如果内存位置的值与预期值相等,则将新值写入该位置;否则,操作失败。

using System;
using System.Threading;class Program
{static int sharedValue = 0;static void Main(string[] args){// 使用 CAS 算法更新共享变量int expectedValue = 0;int newValue = 1;if (Interlocked.CompareExchange(ref sharedValue, newValue, expectedValue) == expectedValue){Console.WriteLine("Value updated successfully.");}else{Console.WriteLine("Value update failed.");}}
}

在代码中,Interlocked.CompareExchange 方法用于比较并交换操作,它原子性地比较 sharedValue
的值是否等于 expectedValue,如果相等则将 newValue 写入
sharedValue,并返回原来的值;否则不做任何操作。通过这种方式,我们可以实现无锁的并发控制,避免了锁带来的开销和竞争。

CAS 算法通常用于实现无锁的数据结构,例如无锁队列、无锁栈等。虽然 CAS 算法能够提供较好的并发性能,但在某些场景下可能会存在ABA问题等限制,需要特殊处理。

4.2.2 Volatile 关键字

Volatile 关键字用于声明字段是易变的,即可能被多个线程同时访问。它可以确保变量的读取和写入操作都是原子性的,并且不会被编译器或者 CPU 优化掉,从而避免了线程间的数据不一致性问题。

using System;
using System.Threading;class Program
{private static volatile bool _flag = false;static void Main(string[] args){// 启动一个线程不断修改 _flag 的值Thread writerThread = new Thread(WriteFlag);writerThread.Start();// 主线程读取 _flag 的值while (true){if (_flag){Console.WriteLine("Flag is true.");break;}else{Console.WriteLine("Flag is false.");Thread.Sleep(1000);}}}static void WriteFlag(){// 在另一个线程中修改 _flag 的值Thread.Sleep(2000);_flag = true;Console.WriteLine("Flag has been set to true.");}
}

在代码中,使用了 volatile 关键字来声明 _flag 字段,确保了其在多线程环境下的可见性和原子性。主线程不断读取 _flag 的值,而另一个线程在一段时间后将其设置为 true。由于使用了 volatile 关键字,主线程能够正确地读取到 _flag 字段的最新值,从而实现了线程间的正确通信。

4.3 无锁并发编程的优势

  • 减少线程切换开销:无锁并发编程不涉及线程的阻塞和唤醒,可以减少线程切换的开销,提高程序性能。
  • 没有死锁风险:由于无锁并发编程不需要使用锁机制,因此不存在死锁等与锁相关的问题。

4.4 无锁并发编程的局限性

  • 实现复杂度较高:无锁并发编程通常需要仔细设计和实现,因此可能比使用锁机制更复杂。
  • 适用场景有限:无锁并发编程适用于某些特定的场景,例如高并发读操作、轻量级状态同步等。

无锁并发编程是一种重要的并发控制方式,可以提高程序的性能和可伸缩性。但在实际应用中,我们需要根据具体情况选择合适的并发控制方式,以确保程序的正确性和性能。

五、并发集合类

在 C# 中,.NET Framework 提供了许多线程安全的并发集合类,包括 ConcurrentBag、ConcurrentDictionary、ConcurrentQueue 和 ConcurrentStack。本章将介绍这些并发集合类的特点、用途以及示例代码。

5.1 ConcurrentBag

ConcurrentBag 是一个无序的、线程安全的集合类,用于存储对象。它允许多个线程同时添加、移除和遍历元素,适用于需要高度并发性的场景。

using System;
using System.Collections.Concurrent;
using System.Threading.Tasks;class Program
{static void Main(string[] args){ConcurrentBag<int> bag = new ConcurrentBag<int>();// 使用多个线程添加元素到 ConcurrentBagParallel.For(0, 10, i =>{bag.Add(i);Console.WriteLine($"Added {i} to bag.");});// 遍历 ConcurrentBag 中的元素foreach (var item in bag){Console.WriteLine($"Item in bag: {item}");}}
}

5.2 ConcurrentDictionary

ConcurrentDictionary 是一个线程安全的字典集合类,用于存储键值对。它允许多个线程同时对字典进行读取、写入和修改操作,提供了高效的并发性能。

using System;
using System.Collections.Concurrent;class Program
{static void Main(string[] args){ConcurrentDictionary<int, string> dictionary = new ConcurrentDictionary<int, string>();// 使用多个线程添加元素到 ConcurrentDictionaryParallel.For(0, 10, i =>{dictionary.TryAdd(i, i);Console.WriteLine($"{i} Added");});// 读取 ConcurrentDictionary 中的键值对foreach (var kvp in dictionary){Console.WriteLine($"Key: {kvp.Key}, Value: {kvp.Value}");}}
}

5.3 ConcurrentQueue

ConcurrentQueue 是一个线程安全的队列集合类,用于存储对象。它支持多个线程同时对队列进行入队和出队操作,并提供了高效的并发性能。

using System;
using System.Collections.Concurrent;
using System.Threading.Tasks;class Program
{static void Main(string[] args){ConcurrentQueue<int> queue = new ConcurrentQueue<int>();// 使用多个线程入队Parallel.For(0, 10, i =>{queue.Enqueue(i);Console.WriteLine($"Enqueued {i} to queue.");});// 多个线程出队int item;while (queue.TryDequeue(out item)){Console.WriteLine($"Dequeued {item} from queue.");}}
}

5.4 ConcurrentStack

ConcurrentStack 是一个线程安全的栈集合类,用于存储对象。它支持多个线程同时对栈进行入栈和出栈操作,并提供了高效的并发性能。

using System;
using System.Collections.Concurrent;
using System.Threading.Tasks;class Program
{static void Main(string[] args){ConcurrentStack<int> stack = new ConcurrentStack<int>();// 使用多个线程入栈Parallel.For(0, 10, i =>{stack.Push(i);Console.WriteLine($"Pushed {i} to stack.");});// 多个线程出栈int item;while (stack.TryPop(out item)){Console.WriteLine($"Popped {item} from stack.");}}
}

六、经典并发同步问题

以下是几个经典的多线程并发同步问题

6.1 生产者-消费者问题(Producer-Consumer Problem)

生产者线程生成数据并放入共享缓冲区,消费者线程从缓冲区中取出数据进行消费。需要确保在生产者线程生产数据时,消费者线程不会访问空缓冲区,并且在消费者线程消费数据时,生产者线程不会访问满缓冲区。

6.1.1 使用 Monitor 类实现生产者-消费者问题

using System;
using System.Threading;class Program
{static int[] buffer = new int[10];static int count = 0;static object locker = new object();static void Main(string[] args){Thread producerThread = new Thread(Producer);Thread consumerThread = new Thread(Consumer);producerThread.Start();consumerThread.Start();producerThread.Join();consumerThread.Join();}static void Producer(){for (int i = 0; i < 20; i++){lock (locker){while (count == buffer.Length)Monitor.Wait(locker);buffer[count++] = i;Console.WriteLine("Produced: " + i);Monitor.PulseAll(locker);}}}static void Consumer(){for (int i = 0; i < 20; i++){lock (locker){while (count == 0)Monitor.Wait(locker);int consumed = buffer[--count];Console.WriteLine("Consumed: " + consumed);Monitor.PulseAll(locker);}}}
}

6.1.2 使用 Semaphore 类实现生产者-消费者问题

using System;
using System.Threading;class Program
{static int[] buffer = new int[10];static SemaphoreSlim empty = new SemaphoreSlim(10);static SemaphoreSlim full = new SemaphoreSlim(0);static object locker = new object();static void Main(string[] args){Thread producerThread = new Thread(Producer);Thread consumerThread = new Thread(Consumer);producerThread.Start();consumerThread.Start();producerThread.Join();consumerThread.Join();}static void Producer(){for (int i = 0; i < 20; i++){empty.Wait();lock (locker){buffer[i % buffer.Length] = i;Console.WriteLine("Produced: " + i);}full.Release();}}static void Consumer(){for (int i = 0; i < 20; i++){full.Wait();lock (locker){int consumed = buffer[i % buffer.Length];Console.WriteLine("Consumed: " + consumed);}empty.Release();}}
}

6.1.3 使用 BlockingCollection 类实现生产者-消费者问题

using System;
using System.Collections.Concurrent;
using System.Threading;class Program
{static BlockingCollection<int> buffer = new BlockingCollection<int>(10);static void Main(string[] args){Thread producerThread = new Thread(Producer);Thread consumerThread = new Thread(Consumer);producerThread.Start();consumerThread.Start();producerThread.Join();consumerThread.Join();}static void Producer(){for (int i = 0; i < 20; i++){buffer.Add(i);Console.WriteLine("Produced: " + i);}buffer.CompleteAdding();}static void Consumer(){foreach (var item in buffer.GetConsumingEnumerable()){Console.WriteLine("Consumed: " + item);}}
}

这些示例分别使用了 MonitorSemaphoreBlockingCollection 来解决生产者-消费者问题。每个示例都实现了在生产者线程生成数据时,消费者线程不会访问空缓冲区,并且在消费者线程消费数据时,生产者线程不会访问满缓冲区。

6.2 读者-写者问题(Reader-Writer Problem)

多个读者线程可以同时读取共享资源,但写者线程在写入共享资源时需要独占访问。需要确保在有写者写入时,不允许读者读取,以保证数据的一致性。

6.2.1 使用 ReaderWriterLockSlim 类实现读者-写者问题

using System;
using System.Threading;class Program
{static ReaderWriterLockSlim rwLock = new ReaderWriterLockSlim();static int resource = 0;static void Main(string[] args){Thread[] readers = new Thread[5];Thread[] writers = new Thread[2];for (int i = 0; i < 5; i++){readers[i] = new Thread(new ThreadStart(Reader));readers[i].Start();}for (int i = 0; i < 2; i++){writers[i] = new Thread(new ThreadStart(Writer));writers[i].Start();}for (int i = 0; i < 5; i++){readers[i].Join();}for (int i = 0; i < 2; i++){writers[i].Join();}}static void Reader(){while (true){rwLock.EnterReadLock();Console.WriteLine("Reader " + Thread.CurrentThread.ManagedThreadId + " reads: " + resource);rwLock.ExitReadLock();Thread.Sleep(1000);}}static void Writer(){while (true){rwLock.EnterWriteLock();resource++;Console.WriteLine("Writer " + Thread.CurrentThread.ManagedThreadId + " writes: " + resource);rwLock.ExitWriteLock();Thread.Sleep(2000);}}
}

6.2.2 使用 SemaphoreSlim 类实现读者-写者问题

using System;
using System.Threading;class Program
{static SemaphoreSlim readLock = new SemaphoreSlim(1);static SemaphoreSlim writeLock = new SemaphoreSlim(1);static int readersCount = 0;static int resource = 0;static void Main(string[] args){Thread[] readers = new Thread[5];Thread[] writers = new Thread[2];for (int i = 0; i < 5; i++){readers[i] = new Thread(new ThreadStart(Reader));readers[i].Start();}for (int i = 0; i < 2; i++){writers[i] = new Thread(new ThreadStart(Writer));writers[i].Start();}for (int i = 0; i < 5; i++){readers[i].Join();}for (int i = 0; i < 2; i++){writers[i].Join();}}static void Reader(){while (true){readLock.Wait();readersCount++;if (readersCount == 1)writeLock.Wait();readLock.Release();Console.WriteLine("Reader " + Thread.CurrentThread.ManagedThreadId + " reads: " + resource);readLock.Wait();readersCount--;if (readersCount == 0)writeLock.Release();readLock.Release();Thread.Sleep(1000);}}static void Writer(){while (true){writeLock.Wait();resource++;Console.WriteLine("Writer " + Thread.CurrentThread.ManagedThreadId + " writes: " + resource);writeLock.Release();Thread.Sleep(2000);}}
}

6.2.3 使用 Monitor 类实现读者-写者问题

using System;
using System.Threading;class Program
{static object lockObj = new object();static int readersCount = 0;static int resource = 0;static void Main(string[] args){Thread[] readers = new Thread[5];Thread[] writers = new Thread[2];for (int i = 0; i < 5; i++){readers[i] = new Thread(new ThreadStart(Reader));readers[i].Start();}for (int i = 0; i < 2; i++){writers[i] = new Thread(new ThreadStart(Writer));writers[i].Start();}for (int i = 0; i < 5; i++){readers[i].Join();}for (int i = 0; i < 2; i++){writers[i].Join();}}static void Reader(){while (true){lock (lockObj){readersCount++;if (readersCount == 1)Monitor.Enter(lockObj);}Console.WriteLine("Reader " + Thread.CurrentThread.ManagedThreadId + " reads: " + resource);lock (lockObj){readersCount--;if (readersCount == 0)Monitor.Exit(lockObj);}Thread.Sleep(1000);}}static void Writer(){while (true){Monitor.Enter(lockObj);resource++;Console.WriteLine("Writer " + Thread.CurrentThread.ManagedThreadId + " writes: " + resource);Monitor.Exit(lockObj);Thread.Sleep(2000);}}
}

6.3 哲学家就餐问题(Dining Philosophers Problem)

五位哲学家围坐在一张圆桌旁,每位哲学家前面有一只筷子。哲学家思考和进餐,但只有同时拿到两只筷子时才能进餐,而筷子必须是干净的。需要解决资源竞争和死锁的问题。

6.3.1 使用Semaphore实现哲学家就餐问题

using System;
using System.Threading;class Program
{static Semaphore[] sticks = new Semaphore[5];static Semaphore table = new Semaphore(4, 4);static void Main(string[] args){for (int i = 0; i < 5; i++){sticks[i] = new Semaphore(1, 1);}Thread[] philosophers = new Thread[5];for (int i = 0; i < 5; i++){philosophers[i] = new Thread(Philosopher);philosophers[i].Start(i);}for (int i = 0; i < 5; i++){philosophers[i].Join();}}static void Philosopher(object id){int philosopherId = (int)id;while (true){// 思考Console.WriteLine($"Philosopher {philosopherId} is thinking.");// 拿筷子table.WaitOne();sticks[philosopherId].WaitOne();sticks[(philosopherId + 1) % 5].WaitOne();// 吃饭Console.WriteLine($"Philosopher {philosopherId} is eating.");// 放筷子sticks[philosopherId].Release();sticks[(philosopherId + 1) % 5].Release();table.Release();Thread.Sleep(2000);}}
}

6.3.2 使用Mutex实现哲学家就餐问题

using System;
using System.Threading;class Program
{static Mutex[] sticks = new Mutex[5];static void Main(string[] args){for (int i = 0; i < 5; i++){sticks[i] = new Mutex();}Thread[] philosophers = new Thread[5];for (int i = 0; i < 5; i++){philosophers[i] = new Thread(Philosopher);philosophers[i].Start(i);}for (int i = 0; i < 5; i++){philosophers[i].Join();}}static void Philosopher(object id){int philosopherId = (int)id;while (true){// 思考Console.WriteLine($"Philosopher {philosopherId} is thinking.");// 拿筷子sticks[philosopherId].WaitOne();sticks[(philosopherId + 1) % 5].WaitOne();// 吃饭Console.WriteLine($"Philosopher {philosopherId} is eating.");// 放筷子sticks[philosopherId].ReleaseMutex();sticks[(philosopherId + 1) % 5].ReleaseMutex();Thread.Sleep(2000);}}
}

6.3.3 使用Monitor实现哲学家就餐问题

using System;
using System.Threading;class Program
{static object[] sticks = new object[5];static void Main(string[] args){for (int i = 0; i < 5; i++){sticks[i] = new object();}Thread[] philosophers = new Thread[5];for (int i = 0; i < 5; i++){philosophers[i] = new Thread(Philosopher);philosophers[i].Start(i);}for (int i = 0; i < 5; i++){philosophers[i].Join();}}static void Philosopher(object id){int philosopherId = (int)id;while (true){// 思考Console.WriteLine($"Philosopher {philosopherId} is thinking.");lock (sticks[philosopherId]){// 拿左边筷子Monitor.Enter(sticks[philosopherId]);// 拿右边筷子Monitor.Enter(sticks[(philosopherId + 1) % 5]);// 吃饭Console.WriteLine($"Philosopher {philosopherId} is eating.");// 放筷子Monitor.Exit(sticks[philosopherId]);Monitor.Exit(sticks[(philosopherId + 1) % 5]);}Thread.Sleep(2000);}}
}

总结

本文简要探讨了 C# 中的多线程编程技术,重点介绍了锁的基本概念、线程锁的类型、锁的实现方式、无锁并发编程以及 C# 中的并发集合类和经典并发同步问题。通过学习本文,我们可以获得以下几个方面的收获:

  1. 理解多线程编程的基本概念:通过介绍锁的基本概念和原理,可以了解为什么在多线程编程中需要使用锁,以及锁是如何工作的。

  2. 掌握不同类型的线程锁:通过对自旋锁、互斥锁、混合锁和读写锁的介绍,可以了解各种锁的特点、适用场景和实现方式,以便在实际应用中选择合适的锁机制。

  3. 熟悉锁的实现方式:通过对 Monitor、Mutex、Semaphore 和 ReaderWriterLock 的介绍,可以了解不同锁的底层实现原理和使用方法,从而更好地应用于实际开发中。

  4. 了解无锁并发编程:通过介绍无锁算法和无锁并发编程的优势和局限性,可以了解在某些场景下无锁编程可以提供更好的性能和并发能力。

  5. 熟悉 C# 中的并发集合类:通过介绍 ConcurrentBag、ConcurrentDictionary、ConcurrentQueue 和 ConcurrentStack
    等并发集合类,可以了解如何安全地在多线程环境中使用集合类。

  6. 解决经典并发同步问题:通过介绍生产者-消费者问题、读者-写者问题和哲学家就餐问题的解决方案,可以了解如何使用线程锁来解决实际的并发同步问题。

通过本文的学习,可以更加深入地理解并发编程的相关知识,掌握多线程编程的技巧,提高程序的性能和稳定性。

相关文章:

C# 多线程编程:线程锁与无锁并发

文章目录 前言一、锁的基本概念1.1 什么是锁&#xff1f;1.2 为什么需要锁&#xff1f;1.3 锁的作用原理 二、线程锁的类型2.1 自旋锁&#xff08;Spin Lock&#xff09;2.2 互斥锁&#xff08;Mutex&#xff09;2.3 混合锁&#xff08;Hybrid Lock&#xff09;2.4 读写锁&…...

React.FC

React.FC 是 React 中的一个类型别名&#xff0c;代表“函数组件”。它是一个接受 props&#xff08;属性&#xff09;并返回 JSX 元素的函数。 type React.FC<P {}> (props: P) > ReactElement | null;其中&#xff1a;P 是一个可选的泛型类型参数&#xff0c;表示…...

使用pytorch构建一个无监督的深度卷积GAN网络模型

本文为此系列的第二篇DCGAN&#xff0c;上一篇为初级的GAN。普通GAN有训练不稳定、容易陷入局部最优等问题&#xff0c;DCGAN相对于普通GAN的优点是能够生成更加逼真、清晰的图像。 因为DCGAN是在GAN的基础上的改造&#xff0c;所以本篇只针对GAN的改造点进行讲解&#xff0c;其…...

[AI]文心一言出圈的同时,NLP处理下的ChatGPT-4.5最新资讯

AI文心一言出圈的同时&#xff0c;NLP处理下的ChatGPT-4.5最新资讯 1.背景介绍 随着人工智能技术的不断发展&#xff0c;自然语言处理&#xff08;NLP&#xff09;技术在近年来取得了显著的进步。其中&#xff0c;聊天机器人技术作为NLP领域的一个重要应用&#xff0c;已经广…...

vue.js设计与实现(分支切换与cleanup)

如存在三元运算符时&#xff0c;怎么处理 // 原始数据 const data { text: hello world,ok:true}// 副作用函数存在三元运算符 effect(function effectFn(){document.body.innerText obj.ok ? obj.text : not })// 理解如此&#xff0c;obj.ok和obj.text都会绑定effectFn函…...

206基于matlab的无人机航迹规划(UAV track plannin)

基于matlab的无人机航迹规划(UAV track plannin&#xff09;。输入输出参数包括 横滚、俯仰、航向角&#xff08;单位&#xff1a;度&#xff09;&#xff1b;横滚速率、俯仰速率、航向角速率&#xff08;单位&#xff1a;度/秒&#xff09;&#xff1b;飞机运动速度——X右翼、…...

【Linux 】查看veth-pair对的映射关系

1. 查看当前存在的ns ip netns add netns199 //新建一个命名空间 # ip netns show netns199 (id: 3)可以看到一个名称叫做netns199 的命名空间&#xff0c;其 id为3 2. 创建一个对&#xff0c;并加入其中一个到其他命名空间中 $ sudo ip link add veth100 type veth peer n…...

Cisco Firepower FMCv修改管理Ip方法

FMCv 是部署在VMWARE虚拟平台上的FMC 部署完成后&#xff0c;如何修改管理IP 1 查看当前版本 show version 可以看到是for VMware 2 修改管理IP步骤 2.1 进入expert模式 expert2.2 进入超级用户 sudo su并输入密码 2.3 查看当前网卡Ip 2.4 修改Ip 命令&#xff1a; /…...

PHP开发全新29网课交单平台源码修复全开源版本,支持聚合登陆易支付

这是一套最新版本的PHP开发的网课交单平台源代码&#xff0c;已进行全开源修复&#xff0c;支持聚合登录和易支付功能。 项目 地 址 &#xff1a; runruncode.com/php/19721.html 以下是对该套代码的主要更新和修复&#xff1a; 1. 移除了论文编辑功能。 2. 移除了强国接码…...

【Web前端】CSS基本语法规范和引入方式常见选择器用法常见元素属性

一、基本语法规范 选择器 {一条/N条声明} 选择器决定针对谁修改 (找谁) 声明决定修改什么.。(干什么) 声明的属性是键值对.。使用 &#xff1a; 区分键值对&#xff0c; 使用 &#xff1a; 区分键和值。 <!DOCTYPE html> <html lang"en"> <head>&…...

SnapGene 5 for Mac 分子生物学软件

SnapGene 5 for Mac是一款专为Mac操作系统设计的分子生物学软件&#xff0c;以其强大的功能和用户友好的界面&#xff0c;为科研人员提供了高效、便捷的基因克隆和分子实验设计体验。 软件下载&#xff1a;SnapGene 5 for Mac v5.3.1中文激活版 这款软件支持DNA构建和克隆设计&…...

本地部署大模型的几种工具(上-相关使用)

目录 前言 为什么本地部署 目前的工具 vllm 介绍 下载模型 安装vllm 运行 存在问题 chatglm.cpp 介绍 下载 安装 运行 命令行运行 webdemo运行 GPU推理 ollama 介绍 下载 运行 运行不同参数量的模型 存在问题 lmstudio 介绍 下载 使用 下载模型文件…...

Spring Boot集成itext实现html生成PDF功能

1.itext介绍 iText是著名的开放源码的站点sourceforge一个项目,是用于生成PDF文档的一个java类库。通过iText不仅可以生成PDF或rtf的文档,而且可以将XML、Html文件转化为PDF文件 iText 的特点 以下是 iText 库的显着特点 − Interactive − iText 为你提供类(API)来生成…...

Java 多态、包、final、权限修饰符、静态代码块

多态 Java多态是指一个对象可以具有多种形态。它是面向对象编程的一个重要特性&#xff0c;允许子类对象可以被当作父类对象使用。多态的实现主要依赖于继承、接口和方法重写。 在Java中&#xff0c;多态的实现主要通过以下两种方式&#xff1a; 继承&#xff1a;子类继承父类…...

基于Spring boot + Vue协同过滤算法的电影推荐系统

末尾获取源码作者介绍&#xff1a;大家好&#xff0c;我是墨韵&#xff0c;本人4年开发经验&#xff0c;专注定制项目开发 更多项目&#xff1a;CSDN主页YAML墨韵 学如逆水行舟&#xff0c;不进则退。学习如赶路&#xff0c;不能慢一步。 目录 一、项目简介 二、开发技术与环…...

Chrome之解决:浏览器插件不能使用问题(十三)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…...

【正版特惠】IDM 永久授权 优惠低至109元!

尽管小编有修改版IDM&#xff0c;但是由于软件太好用了&#xff0c;很多同学干脆就直接购买了正版&#xff0c;现在正版也不贵&#xff0c;并且授权码绑定自己的邮箱&#xff0c;直接官方下载激活&#xff0c;无需其他的绿化修改之类的操作&#xff0c;不喜欢那么麻烦的&#x…...

SpringBoot与Prometheus监控整合

参考&#xff1a; springboot实战之prometheus监控整合-腾讯云开发者社区-腾讯云 https://www.cnblogs.com/skevin/p/15874139.html https://www.jianshu.com/p/e5dc2b45c7a4...

Linux 系统 docker搭建LNMP环境

1、安装nginx docker pull nginx (默认安装的是最新版本) 2、运行nginx docker run --name nginx -p 80:80 -d nginx:latest 备注&#xff1a;--name nginx 表示容器名为 nginx -d 表示后台运行 -p 80:80 表示把本地80端口绑定到Nginx服务端的 80端口 nginx:lates…...

拉普拉斯变换

定义&#xff1a; 拉普拉斯变换是一种在信号处理、控制理论和其他领域中广泛使用的数学工具&#xff0c;用于将一个函数从时域转换到复频域。拉普拉斯变换将一个函数 f(t) 变换为一个复变量函数 F(s)&#xff0c;其中 s 是复数变量。下面是拉普拉斯变换的推导过程&#xff1a;…...

Flask RESTful 示例

目录 1. 环境准备2. 安装依赖3. 修改main.py4. 运行应用5. API使用示例获取所有任务获取单个任务创建新任务更新任务删除任务 中文乱码问题&#xff1a; 下面创建一个简单的Flask RESTful API示例。首先&#xff0c;我们需要创建环境&#xff0c;安装必要的依赖&#xff0c;然后…...

centos 7 部署awstats 网站访问检测

一、基础环境准备&#xff08;两种安装方式都要做&#xff09; bash # 安装必要依赖 yum install -y httpd perl mod_perl perl-Time-HiRes perl-DateTime systemctl enable httpd # 设置 Apache 开机自启 systemctl start httpd # 启动 Apache二、安装 AWStats&#xff0…...

Ascend NPU上适配Step-Audio模型

1 概述 1.1 简述 Step-Audio 是业界首个集语音理解与生成控制一体化的产品级开源实时语音对话系统&#xff0c;支持多语言对话&#xff08;如 中文&#xff0c;英文&#xff0c;日语&#xff09;&#xff0c;语音情感&#xff08;如 开心&#xff0c;悲伤&#xff09;&#x…...

Spring AI 入门:Java 开发者的生成式 AI 实践之路

一、Spring AI 简介 在人工智能技术快速迭代的今天&#xff0c;Spring AI 作为 Spring 生态系统的新生力量&#xff0c;正在成为 Java 开发者拥抱生成式 AI 的最佳选择。该框架通过模块化设计实现了与主流 AI 服务&#xff08;如 OpenAI、Anthropic&#xff09;的无缝对接&…...

实现弹窗随键盘上移居中

实现弹窗随键盘上移的核心思路 在Android中&#xff0c;可以通过监听键盘的显示和隐藏事件&#xff0c;动态调整弹窗的位置。关键点在于获取键盘高度&#xff0c;并计算剩余屏幕空间以重新定位弹窗。 // 在Activity或Fragment中设置键盘监听 val rootView findViewById<V…...

Linux --进程控制

本文从以下五个方面来初步认识进程控制&#xff1a; 目录 进程创建 进程终止 进程等待 进程替换 模拟实现一个微型shell 进程创建 在Linux系统中我们可以在一个进程使用系统调用fork()来创建子进程&#xff0c;创建出来的进程就是子进程&#xff0c;原来的进程为父进程。…...

深度学习水论文:mamba+图像增强

&#x1f9c0;当前视觉领域对高效长序列建模需求激增&#xff0c;对Mamba图像增强这方向的研究自然也逐渐火热。原因在于其高效长程建模&#xff0c;以及动态计算优势&#xff0c;在图像质量提升和细节恢复方面有难以替代的作用。 &#x1f9c0;因此短时间内&#xff0c;就有不…...

C++课设:简易日历程序(支持传统节假日 + 二十四节气 + 个人纪念日管理)

名人说:路漫漫其修远兮,吾将上下而求索。—— 屈原《离骚》 创作者:Code_流苏(CSDN)(一个喜欢古诗词和编程的Coder😊) 专栏介绍:《编程项目实战》 目录 一、为什么要开发一个日历程序?1. 深入理解时间算法2. 练习面向对象设计3. 学习数据结构应用二、核心算法深度解析…...

AI语音助手的Python实现

引言 语音助手(如小爱同学、Siri)通过语音识别、自然语言处理(NLP)和语音合成技术,为用户提供直观、高效的交互体验。随着人工智能的普及,Python开发者可以利用开源库和AI模型,快速构建自定义语音助手。本文由浅入深,详细介绍如何使用Python开发AI语音助手,涵盖基础功…...

【Veristand】Veristand环境安装教程-Linux RT / Windows

首先声明&#xff0c;此教程是针对Simulink编译模型并导入Veristand中编写的&#xff0c;同时需要注意的是老用户编译可能用的是Veristand Model Framework&#xff0c;那个是历史版本&#xff0c;且NI不会再维护&#xff0c;新版本编译支持为VeriStand Model Generation Suppo…...