免费的微网站制作平台/网络推广项目外包公司
文章目录
- 前言
- 一、锁的基本概念
- 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);}}
}
这些示例分别使用了 Monitor
、Semaphore
和 BlockingCollection
来解决生产者-消费者问题。每个示例都实现了在生产者线程生成数据时,消费者线程不会访问空缓冲区,并且在消费者线程消费数据时,生产者线程不会访问满缓冲区。
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# 中的并发集合类和经典并发同步问题。通过学习本文,我们可以获得以下几个方面的收获:
理解多线程编程的基本概念:通过介绍锁的基本概念和原理,可以了解为什么在多线程编程中需要使用锁,以及锁是如何工作的。
掌握不同类型的线程锁:通过对自旋锁、互斥锁、混合锁和读写锁的介绍,可以了解各种锁的特点、适用场景和实现方式,以便在实际应用中选择合适的锁机制。
熟悉锁的实现方式:通过对 Monitor、Mutex、Semaphore 和 ReaderWriterLock 的介绍,可以了解不同锁的底层实现原理和使用方法,从而更好地应用于实际开发中。
了解无锁并发编程:通过介绍无锁算法和无锁并发编程的优势和局限性,可以了解在某些场景下无锁编程可以提供更好的性能和并发能力。
熟悉 C# 中的并发集合类:通过介绍 ConcurrentBag、ConcurrentDictionary、ConcurrentQueue 和 ConcurrentStack
等并发集合类,可以了解如何安全地在多线程环境中使用集合类。解决经典并发同步问题:通过介绍生产者-消费者问题、读者-写者问题和哲学家就餐问题的解决方案,可以了解如何使用线程锁来解决实际的并发同步问题。
通过本文的学习,可以更加深入地理解并发编程的相关知识,掌握多线程编程的技巧,提高程序的性能和稳定性。
相关文章:

C# 多线程编程:线程锁与无锁并发
文章目录 前言一、锁的基本概念1.1 什么是锁?1.2 为什么需要锁?1.3 锁的作用原理 二、线程锁的类型2.1 自旋锁(Spin Lock)2.2 互斥锁(Mutex)2.3 混合锁(Hybrid Lock)2.4 读写锁&…...

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

使用pytorch构建一个无监督的深度卷积GAN网络模型
本文为此系列的第二篇DCGAN,上一篇为初级的GAN。普通GAN有训练不稳定、容易陷入局部最优等问题,DCGAN相对于普通GAN的优点是能够生成更加逼真、清晰的图像。 因为DCGAN是在GAN的基础上的改造,所以本篇只针对GAN的改造点进行讲解,其…...

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

vue.js设计与实现(分支切换与cleanup)
如存在三元运算符时,怎么处理 // 原始数据 const data { text: hello world,ok:true}// 副作用函数存在三元运算符 effect(function effectFn(){document.body.innerText obj.ok ? obj.text : not })// 理解如此,obj.ok和obj.text都会绑定effectFn函…...

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

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

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

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

【Web前端】CSS基本语法规范和引入方式常见选择器用法常见元素属性
一、基本语法规范 选择器 {一条/N条声明} 选择器决定针对谁修改 (找谁) 声明决定修改什么.。(干什么) 声明的属性是键值对.。使用 : 区分键值对, 使用 : 区分键和值。 <!DOCTYPE html> <html lang"en"> <head>&…...

SnapGene 5 for Mac 分子生物学软件
SnapGene 5 for Mac是一款专为Mac操作系统设计的分子生物学软件,以其强大的功能和用户友好的界面,为科研人员提供了高效、便捷的基因克隆和分子实验设计体验。 软件下载: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多态是指一个对象可以具有多种形态。它是面向对象编程的一个重要特性,允许子类对象可以被当作父类对象使用。多态的实现主要依赖于继承、接口和方法重写。 在Java中,多态的实现主要通过以下两种方式: 继承:子类继承父类…...

基于Spring boot + Vue协同过滤算法的电影推荐系统
末尾获取源码作者介绍:大家好,我是墨韵,本人4年开发经验,专注定制项目开发 更多项目:CSDN主页YAML墨韵 学如逆水行舟,不进则退。学习如赶路,不能慢一步。 目录 一、项目简介 二、开发技术与环…...

Chrome之解决:浏览器插件不能使用问题(十三)
简介: CSDN博客专家,专注Android/Linux系统,分享多mic语音方案、音视频、编解码等技术,与大家一起成长! 优质专栏:Audio工程师进阶系列【原创干货持续更新中……】🚀 优质专栏:多媒…...

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

SpringBoot与Prometheus监控整合
参考: 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 备注:--name nginx 表示容器名为 nginx -d 表示后台运行 -p 80:80 表示把本地80端口绑定到Nginx服务端的 80端口 nginx:lates…...

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

Mashup-Math_Topic_One
Tutorial and Introspection A Rudolf and 121 注意到第 1 1 1 位只能被第 2 2 2 位影响,以此类推位置,对于 a i a_i ai , 如果 < 0 < 0 <0 ,不合法 ; 否则, a i − a i , a i 1 − 2 ∗ a i , a i 2 − a …...

基于JavaWEB SSM SpringBoot婚纱影楼摄影预约网站设计和实现
基于JavaWEB SSM SpringBoot婚纱影楼摄影预约网站设计和实现 博主介绍:多年java开发经验,专注Java开发、定制、远程、文档编写指导等,csdn特邀作者、专注于Java技术领域 作者主页 央顺技术团队 Java毕设项目精品实战案例《1000套》 欢迎点赞 收藏 ⭐留言…...

逐步学习Go-Select多路复用
概述 这里又有多路复用,但是Go中的这个多路复用不同于网络中的多路复用。在Go里,select用于同时等待多个通信操作(即多个channel的发送或接收操作)。Go中的channel可以参考我的文章:逐步学习Go-并发通道chan(channel)…...

王道:OJ15
课时15作业 Description 读取10个元素 87 7 60 80 59 34 86 99 21 3,然后建立二叉查找树,排序后输出3 7 21 34 59 60 80 86 87 99,针对有序后的元素,存入一个长度为10的数组中,通过折半查找找到21的下标(…...

【案例·查】数据类型强制转换,方便查询匹配
问题描述: MySQL执行中需要将某种数据类型的表达式显式转换为另一种数据类型,可以使用 SQL 中的cast()来处理 案例: SELECT CAST(9.0 AS decimal) #String化为小数类型SELECT * FROM table_1 WHERE 1888-03-07 CAST(theDate AS DATE) …...

spring boot3自定义注解+拦截器+Redis实现高并发接口限流
⛰️个人主页: 蒾酒 🔥系列专栏:《spring boot实战》 🌊山高路远,行路漫漫,终有归途 目录 写在前面 内容简介 实现思路 实现步骤 1.自定义限流注解 2.编写限流拦截器 3.注册拦截器 4.接口限流测试 写在前…...

使用certbot为网站启用https
1. 安装certbot客户端 cd /usr/local/bin wget https://dl.eff.org/certbot-auto chmod ax ./certbot-auto 2. 创建目录和配置nginx用于验证域名 mkdir -p /data/www/letsencryptserver {listen 80;server_name ~^(?<subdomain>.).ninvfeng.com;location /.well-known…...

Unity 背包系统中拖拽物体到指定位置或互换位置效果的实现
在Unity中,背包系统是一种常见的游戏系统,可以用于管理和展示玩家所持有的物品、道具或装备。 其中的拖拽功能非常有意思,具体功能就是玩家可以通过拖拽物品图标来移动物品在背包中的位置,或者将物品拖拽到其他位置或界面中&…...

iOS客户端自动化UI自动化airtest+appium从0到1搭建macos+脚本设计demo演示+全网最全最详细保姆级有步骤有图
Android客户端自动化UI自动化airtest从0到1搭建macos脚本设计demo演示全网最全最详细保姆级有步骤有图-CSDN博客 避坑系列-必读: 不要安装iOS-Tagent ,安装appium -这2个性质其实是差不多的都是为了安装wda。注意安装appium最新版本,安装完…...

每周编辑精选|在线运行 Deepmoney 金融大模型、AI 偏好等多个优质数据集上线
目前,AI 领域对金融模型的研究成果大多是基于公共知识进行训练的,但在实际的金融实践中,这些公共知识对于当前市场的可解释性往往严重不足。一个理想的金融大模型应该能够理解新闻或数据事件,并能够即时地从主观和量化两个角度对事…...