同步的概念
在我们学会在C#中使用线程之后,我们拥有了把一个程序中的不同代码段在不同线程中运行的能力,可以说此时我们已经能够做到让他们分别执行,异步执行。
对于我们的桌面端程序,使用多线程可以让我们在后台进行操作的时候保持用户界面的响应。
对于服务器应用程序,多线程可以让我们单独接收处理每个客户端发来的请求。否则在完全处理完一个请求之前将无法响应其他请求。
但是多线程完全异步意味着线程之间不存在互相配合,此时就必须协调资源的分配和管理,互相之间必须要有协调。
如果多个线程同时访问修改同一个数据,很可能造成数据损坏。
这在数据库程序里的读写操作需要保障安全有异曲同工之处。
线程同步:
是指并发线程,高效有序的访问共享资源的技术。
同步:
同步是指某一时刻,只有一个线程能访问资源,只有当资源所有者放弃了资源或者代码的占有,其他线程才能使用这些资源。
前台线程和后台线程的区别
前台线程只要没有结束,应用程序就不会结束,如果前台线程全部结束,后台线程将自动结束,而不考虑是否完成。
后台线程的属性IsBackgroud需要被设置为True。
实现方式
lock关键字
lock关键字用于确保代码块同一时间只会有一个线程在运行,不会被其他线程中断。
它的使用形式为:以lock关键字开头,有一个作为参数的对象(引用类型数据),后跟一个代码块。此代码块一次只能由一个线程执行。
using System;
using System.Threading;
class Program
{
private static int counter = 0;
private static readonly object lockObject = new object();
static void Main()
{
Thread thread1 = new Thread(IncrementCounter);
Thread thread2 = new Thread(IncrementCounter);
thread1.Start();
thread2.Start();
thread1.Join();
thread2.Join();
Console.WriteLine($"最终计数器值: {counter}");
}
static void IncrementCounter()
{
for (int i = 0; i < 100000; i++)
{
// 使用 lock 来确保此代码块在任意时刻只能由一个线程执行
lock (lockObject)
{
counter++;
}
}
}
}
lock后的参数可以是任意引用类型的对象,但是通常我们用来表示需要进行进程同步的资源,如果一个资源将被多个线程使用,使用对该资源的引用作为lock参数,只要在lock后的代码块内使用该资源,就会是线程安全的,因为这保证了一时间只有一个线程访问该资源。
注意:如果锁定的对象被其他不受我们控制的同步语句锁定,那么就会导致死锁,尤其是public公共类型或者字符串这类会被公共语言库暂留,只要有任意一个线程锁定了这个字符串,就会锁定所有以此字符串为锁的代码块。锁定这类不受我们控制的对象实例,这是非常危险的。
最好锁定:不会被暂留的私有或者受保护成员。
- 简单易用:使用
lock
可以快速实现线程同步。- 自动化异常处理:
lock
结构会自动释放锁,即使在发生异常时,也能够保证锁被正确释放。- 对象锁:
lock
需要一个引用类型的对象作为锁标识,这个对象可以是任何引用类型的实例。
理解上很简单,这个代码块有一把代表了资源的锁,这把锁如果我们进入了代码块(使用了资源),就会锁上。这样其他线程就进不来了,同样的,如果选的锁不好,就会导致很多代码块等一个没必要等的锁(资源)。
lock本质是由下面的Monitor实现的。
Monitor类
此类提供了同步对象的访问机制。
它可以向一个线程授予对象锁用来控制对对象的访问。对象锁提供限制访问代码块的能力。当一个线程获得了这个锁之后,其他线程不能获得该锁。
功能:
- 与某个对象关联
- 是未绑定的,可以从任何上下文直接调用它。
- 不能创建该类实例
方法
Enter:请求获取锁。
Exit:释放锁。
Wait:释放锁并阻塞当前线程,直到接收到另一个线程的通知。
Pulse:通知正在等待的线程它们可以继续。
PulseAll:通知所有等待的线程它们可以继续。
using System;
using System.Threading;
class Program
{
private static readonly object lockObject = new object(); // 任意对象,用于锁定
static void Main()
{
Thread thread1 = new Thread(DoWork);
Thread thread2 = new Thread(DoWork);
thread1.Start();
thread2.Start();
thread1.Join();
thread2.Join();
Console.WriteLine("所有线程已完成工作。");
}
static void DoWork()
{
// 使用 Monitor 的未绑定方法
Monitor.Enter(lockObject); // 这里直接调用静态方法 Enter
try
{
// 临界区代码
Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId} 正在工作...");
Thread.Sleep(1000); // 模拟一些工作
}
finally
{
Monitor.Exit(lockObject); // 确保在任何情况下都释放锁
}
}
}
用Monitor模拟售票系统
using System;
using System.Threading;
class Program
{
private static int totalTickets = 10; // 共享资源:总票数
private static readonly object lockObject = new object(); // 用于同步的锁对象
static void Main()
{
// 创建四个线程模拟购票
Thread thread1 = new Thread(BuyTicket);
Thread thread2 = new Thread(BuyTicket);
Thread thread3 = new Thread(BuyTicket);
Thread thread4 = new Thread(BuyTicket);
// 启动线程
thread1.Start();
thread2.Start();
thread3.Start();
thread4.Start();
// 等待所有线程完成
thread1.Join();
thread2.Join();
thread3.Join();
thread4.Join();
Console.WriteLine("所有线程已完成购票。");
}
static void BuyTicket()
{
while (true)
{
// 使用 Monitor 进行同步
Monitor.Enter(lockObject);
try
{
if (totalTickets > 0)
{
// 模拟购票过程
Console.WriteLine($"线程 {Thread.CurrentThread.ManagedThreadId} 正在购买第 {totalTickets} 张票...");
totalTickets--; // 购买一张票
Thread.Sleep(100); // 模拟一些操作的延迟
}
else
{
// 票已售完,退出循环
Console.WriteLine($"线程 {Thread.CurrentThread.ManagedThreadId} 发现票已售完。");
break;
}
}
finally
{
// 确保释放锁
Monitor.Exit(lockObject);
}
}
}
}
标准输出:线程 5 正在购买第 10 张票...
线程 5 正在购买第 9 张票...
线程 5 正在购买第 8 张票...
线程 5 正在购买第 7 张票...
线程 5 正在购买第 6 张票...
线程 5 正在购买第 5 张票...
线程 5 正在购买第 4 张票...
线程 5 正在购买第 3 张票...
线程 5 正在购买第 2 张票...
线程 5 正在购买第 1 张票...
线程 5 发现票已售完。
线程 3 发现票已售完。
线程 6 发现票已售完。
线程 4 发现票已售完。
所有线程已完成购票。
作为一种锁机制,最好不要锁定公开的或者会被暂留的字符串。
锁定当前的对象实例也是不可靠的,因为可能其他代码只要获得该对象实例的引用就可以锁定这个实例。
Monitor.enter(this);
wait方法:
拥有锁的线程放弃资源并选择等待,可以被下面的通知唤醒。
Monitor.Enter(lockObject);
try
{
while (conditionNotMet)
{
Monitor.Wait(lockObject); // 释放锁并等待通知
}
// 执行相关操作
}
finally
{
Monitor.Exit(lockObject); // 确保释放锁
}
pulse方法
唤醒一个在等待此资源的线程。
唤醒的线程不立即获得锁,它会在被唤醒后,重新尝试获取锁。这是因为 Pulse
只是通知一个线程,它仍然需要通过重新获取锁来继续执行。
Monitor.Enter(lockObject);
try
{
// 执行相关操作
Monitor.Pulse(lockObject); // 唤醒一个等待的线程
}
finally
{
Monitor.Exit(lockObject); // 确保释放锁
}
pulseAll方法
同上,但是唤醒所有。
用于需要通知所有等待线程某个状态变化的场景,比如通知多个消费者,数据已经准备好。
Monitor.Enter(lockObject);
try
{
// 执行相关操作
Monitor.PulseAll(lockObject); // 唤醒所有等待的线程
}
finally
{
Monitor.Exit(lockObject); // 确保释放锁
}
TryEnter方法
public static bool TryEnter(object obj);
public static bool TryEnter(object obj, int millisecondsTimeout);
public static bool TryEnter(object obj, TimeSpan timeout);
尝试获取对象锁,可以设定在指定时间内获取锁。返回获取的成功与否的布尔值。
具有超时的等待
public static bool Wait(object obj, int millisecondsTimeout);
public static bool Wait(object obj, TimeSpan timeout);
让线程释放对象锁并等待一定时间自动唤醒(不会立刻开始执行,会重新尝试获得锁)。
Monitor.Enter(lockObject);
try
{
while (conditionNotMet)
{
if (!Monitor.Wait(lockObject, TimeSpan.FromSeconds(5))) // 等待5秒
{
Console.WriteLine("等待超时");
break;
}
}
// 执行相关操作
}
finally
{
Monitor.Exit(lockObject); // 确保释放锁
}
生产消费者模型
using System;
using System.Threading;
class Program
{
private static readonly object lockObject = new object();
private static bool dataAvailable = false;
static void Main()
{
Thread producer = new Thread(Produce);
Thread consumer = new Thread(Consume);
producer.Start();
consumer.Start();
producer.Join();
consumer.Join();
Console.WriteLine("生产者-消费者过程完成。");
}
static void Produce()
{
Monitor.Enter(lockObject);
try
{
Console.WriteLine("生产者:正在生产数据...");
Thread.Sleep(2000); // 模拟生产过程
dataAvailable = true;
Monitor.Pulse(lockObject); // 唤醒等待的消费者线程
Console.WriteLine("生产者:数据已生产,通知消费者。");
}
finally
{
Monitor.Exit(lockObject); // 确保释放锁
}
}
static void Consume()
{
Monitor.Enter(lockObject);
try
{
while (!dataAvailable)
{
Console.WriteLine("消费者:等待数据...");
bool signaled = Monitor.Wait(lockObject, TimeSpan.FromSeconds(3)); // 等待生产者通知,最多3秒
if (!signaled)
{
Console.WriteLine("消费者:等待超时,尝试重新获取锁...");
}
}
Console.WriteLine("消费者:获取数据并进行消费。");
}
finally
{
Monitor.Exit(lockObject); // 确保释放锁
}
}
}
Mutex类
同步基元。基本想法和作用类似于我们的Monitor类,但是它可以实现跨进程的线程同步。
它只向一个线程授予对共享资源的获取权限,如果一个线程获取了互斥体,则其他想要这个互斥体的线程将被挂起。
它的本质是对win32的封装,比Monitor更强大,但是不适合用于进程内的同步,因为这样会浪费一些系统转换的资源。Monitor会更好的利用资源。
基本使用
和Monitor类似,在进程内,使用Mutex实例获取锁,或者释放锁。
这里和Monitor不同的是,这里没有Enter方法来获取锁,就是直接阻塞然后等待锁(资源)。Monitor获取锁之后还能使用Wait方法等待资源,同时让出锁,但是我们这里就只直接释放锁即可。
可以这么理解:Monitor可以获取,可以释放,可以释放在等待重新获取,可以通知其他正在等待这个资源的线程。
Mutex也可以获取,也可以释放,可以释放再等待其他资源(注意是其他资源),可以一次性等待多个资源。
创建 Mutex 对象:
Mutex mutex = new Mutex(); 创建一个新的 Mutex 对象。所有需要同步的线程共享这个 Mutex 实例。
请求获得锁 (WaitOne):mutex.WaitOne(); 请求当前线程获得 Mutex 锁。如果锁已经被其他线程占用,当前线程将会阻塞,直到锁被释放。
释放锁 (ReleaseMutex):mutex.ReleaseMutex(); 释放当前线程持有的锁,使其他等待的线程能够获得锁。
using System;
using System.Threading;
class Program
{
// 创建一个 Mutex 对象
private static Mutex mutex = new Mutex();
static void Main()
{
// 启动多个线程来模拟并发访问
for (int i = 0; i < 5; i++)
{
Thread thread = new Thread(AccessSharedResource);
thread.Name = $"线程 {i + 1}";
thread.Start();
}
}
static void AccessSharedResource()
{
Console.WriteLine($"{Thread.CurrentThread.Name} 正在等待进入临界区...");
// 请求获得 Mutex 锁
mutex.WaitOne();
try
{
Console.WriteLine($"{Thread.CurrentThread.Name} 已进入临界区。");
// 模拟对共享资源的访问
Thread.Sleep(2000);
Console.WriteLine($"{Thread.CurrentThread.Name} 正在离开临界区。");
}
finally
{
// 释放 Mutex 锁
mutex.ReleaseMutex();
Console.WriteLine($"{Thread.CurrentThread.Name} 已释放锁。");
}
}
}
输出结果:
线程 3 正在等待进入临界区...
线程 1 正在等待进入临界区...
线程 2 正在等待进入临界区...
线程 3 已进入临界区。
线程 4 正在等待进入临界区...
线程 5 正在等待进入临界区...
线程 3 正在离开临界区。
线程 3 已释放锁。
线程 5 已进入临界区。
线程 5 正在离开临界区。
线程 5 已释放锁。
线程 4 已进入临界区。
线程 4 正在离开临界区。
线程 4 已释放锁。
线程 2 已进入临界区。
线程 2 正在离开临界区。
线程 2 已释放锁。
线程 1 已进入临界区。
线程 1 正在离开临界区。
线程 1 已释放锁。
带超时的Mutex等待锁:
using System;
using System.Threading;
class Program
{
private static Mutex mutex = new Mutex();
static void Main()
{
for (int i = 0; i < 3; i++)
{
Thread thread = new Thread(TryAccessSharedResource);
thread.Name = $"线程 {i + 1}";
thread.Start();
}
}
static void TryAccessSharedResource()
{
Console.WriteLine($"{Thread.CurrentThread.Name} 正在等待进入临界区...");
// 尝试获取锁,超时时间为 3 秒
if (mutex.WaitOne(TimeSpan.FromSeconds(3)))
{
try
{
Console.WriteLine($"{Thread.CurrentThread.Name} 已进入临界区。");
Thread.Sleep(2000); // 模拟操作
}
finally
{
mutex.ReleaseMutex();
Console.WriteLine($"{Thread.CurrentThread.Name} 已释放锁。");
}
}
else
{
Console.WriteLine($"{Thread.CurrentThread.Name} 无法在指定时间内获得锁。");
}
}
}
上面等待锁的逻辑没有变化,只是尝试获取锁,并指定最大等待时间为 3 秒。如果在 3 秒内没有获取到锁,返回 false,否则返回 true 并继续执行。不会被一直阻塞,会提供bool值以供走到另外一个分支。
跨进程使用Mutex
需要使用命名Mutex才行,跨进程即跨应用程序,所以已经超越了我们命名全局变量等的作用范围。
创建Mutex的进程:
using System;
using System.Threading;
class Program
{
static void Main()
{
// 创建一个命名的 Mutex,用于跨进程同步
using (Mutex mutex = new Mutex(false, "Global\\MyNamedMutex"))
{
Console.WriteLine("等待获取跨进程的 Mutex...");
// 尝试获取 Mutex 锁
if (mutex.WaitOne(TimeSpan.FromSeconds(10)))
{
try
{
Console.WriteLine("获取到跨进程的 Mutex,正在执行操作...");
Thread.Sleep(5000); // 模拟操作
}
finally
{
mutex.ReleaseMutex();
Console.WriteLine("已释放跨进程的 Mutex。");
}
}
else
{
Console.WriteLine("无法在指定时间内获取跨进程的 Mutex。");
}
}
}
}
另外一个使用这个Mutex的进程:
using System;
using System.Threading;
class Program
{
static void Main()
{
try
{
// 打开已经存在的命名 Mutex
Mutex existingMutex = Mutex.OpenExisting("Global\\MyNamedMutex");
Console.WriteLine("成功打开已存在的命名 Mutex。");
}
catch (WaitHandleCannotBeOpenedException)
{
Console.WriteLine("命名 Mutex 不存在。");
}
}
}
注意事项
开销较大:Mutex 通常比 lock 或 Monitor 开销更大,因为它是内核对象,需要更多的系统资源。仅在需要跨进程同步时才使用 Mutex,否则可以考虑使用 lock 或 Monitor。
防止死锁:确保每次 WaitOne 成功后,使用 ReleaseMutex 释放锁,以防止死锁。
使用 using 语句:在使用命名 Mutex 时,推荐使用 using 语句以确保在异常情况下也能正确释放资源。
Mutex进阶
构造函数
布尔值指示当前线程是否立刻获得此Mutex。string用于指示Mutex命名。
还有一个带有一个输出参数的构造函数,用于输出当前命名的Mutex是否是新的,如果该Mutex已经存在,则返回False,否则为True。
Mutex mutex = new Mutex();
Mutex mutex = new Mutex(true);
Mutex mutex = new Mutex(false, "Global\\MyNamedMutex");
bool createdNew;
Mutex mutex = new Mutex(false, "Global\\MyNamedMutex", out createdNew);
Console.WriteLine($"Mutex 是否新创建: {createdNew}");
WaitHandle是什么
是Mutex,Semaphore等的基类,定义一种用于同步的抽象。
WaitHandle 的常用方法
WaitOne():阻塞当前线程,直到收到信号或超时。该方法用于等待单个 WaitHandle 变为有信号状态。WaitAny(WaitHandle[]):阻塞当前线程,直到任何一个提供的 WaitHandle 变为有信号状态。它在第一个 WaitHandle 变为有信号状态时返回。
WaitAll(WaitHandle[]):阻塞当前线程,直到所有提供的 WaitHandle 都变为有信号状态。它在所有 WaitHandle 变为有信号状态后返回。
更多方法:
Close:显式关闭 Mutex,释放与之相关的所有资源。
OpenExisting:打开一个已存在的命名 Mutex,用于进程间同步。
SignalAndWait:释放一个锁,同时等待另一个锁。
WaitAll:等待所有指定的 WaitHandle 对象被释放。
WaitAny:等待任意一个指定的 WaitHandle 对象被释放。
WaitOne:等待一个 Mutex 对象被释放,这是最常用的等待方法。
WaitAll
和WaitOne一样,要等资源,只不过同时等待所有资源。必须获得所有的锁才会停止阻塞等待。
输出结果:
线程1 尝试获取 Mutex1...
等待所有的 Mutex 被释放...
线程2 尝试获取 Mutex2...
线程3 尝试获取 Mutex3...
线程1 已获取 Mutex1,等待 3 秒后释放...
线程2 已获取 Mutex2,等待 2 秒后释放...
线程3 已获取 Mutex3,等待 1 秒后释放...
线程3 释放 Mutex3。
线程2 释放 Mutex2。
线程1 释放 Mutex1。
所有的 Mutex 都已被释放。
using System;
using System.Threading;
class Program
{
static Mutex mutex1 = new Mutex();
static Mutex mutex2 = new Mutex();
static Mutex mutex3 = new Mutex();
static void Main()
{
// 启动线程来模拟不同的 Mutex 状态
Thread thread1 = new Thread(ThreadProc1);
Thread thread2 = new Thread(ThreadProc2);
Thread thread3 = new Thread(ThreadProc3);
thread1.Start();
thread2.Start();
thread3.Start();
// 等待所有的 Mutex 被释放
Console.WriteLine("等待所有的 Mutex 被释放...");
bool allSignaled = WaitHandle.WaitAll(new WaitHandle[] { mutex1, mutex2, mutex3 }, 10000); // 等待最多 10 秒
if (allSignaled)
{
Console.WriteLine("所有的 Mutex 都已被释放。");
}
else
{
Console.WriteLine("等待超时或不是所有的 Mutex 都已被释放。");
}
thread1.Join();
thread2.Join();
thread3.Join();
}
static void ThreadProc1()
{
Console.WriteLine("线程1 尝试获取 Mutex1...");
mutex1.WaitOne();
Console.WriteLine("线程1 已获取 Mutex1,等待 3 秒后释放...");
Thread.Sleep(3000);
mutex1.ReleaseMutex();
Console.WriteLine("线程1 释放 Mutex1。");
}
static void ThreadProc2()
{
Console.WriteLine("线程2 尝试获取 Mutex2...");
mutex2.WaitOne();
Console.WriteLine("线程2 已获取 Mutex2,等待 2 秒后释放...");
Thread.Sleep(2000);
mutex2.ReleaseMutex();
Console.WriteLine("线程2 释放 Mutex2。");
}
static void ThreadProc3()
{
Console.WriteLine("线程3 尝试获取 Mutex3...");
mutex3.WaitOne();
Console.WriteLine("线程3 已获取 Mutex3,等待 1 秒后释放...");
Thread.Sleep(1000);
mutex3.ReleaseMutex();
Console.WriteLine("线程3 释放 Mutex3。");
}
}
WaitAny
这里主线程等待两个资源,线程1和线程2分别占有一个锁。可以看看输出结果:
等待任意一个 Mutex 被释放...
Mutex 1 已被释放。
线程1 尝试获得 Mutex1...
线程2 尝试获得 Mutex2...
线程2 已获得 Mutex2,等待 3 秒后释放...
线程2 已释放 Mutex2。
using System;
using System.Threading;
class Program
{
static Mutex mutex1 = new Mutex();
static Mutex mutex2 = new Mutex();
static void Main()
{
// 启动两个线程,分别尝试获取锁并在稍后释放它们
Thread thread1 = new Thread(ThreadProc1);
Thread thread2 = new Thread(ThreadProc2);
thread1.Start();
thread2.Start();
// 等待任意一个锁被释放
Console.WriteLine("等待任意一个 Mutex 被释放...");
int index = WaitHandle.WaitAny(new WaitHandle[] { mutex1, mutex2 });
Console.WriteLine($"Mutex {index + 1} 已被释放。");
thread1.Join();
thread2.Join();
}
static void ThreadProc1()
{
// 模拟工作
Thread.Sleep(1000);
Console.WriteLine("线程1 尝试获得 Mutex1...");
mutex1.WaitOne();
Console.WriteLine("线程1 已获得 Mutex1,等待 3 秒后释放...");
Thread.Sleep(3000);
mutex1.ReleaseMutex();
Console.WriteLine("线程1 已释放 Mutex1。");
}
static void ThreadProc2()
{
// 模拟工作
Thread.Sleep(2000);
Console.WriteLine("线程2 尝试获得 Mutex2...");
mutex2.WaitOne();
Console.WriteLine("线程2 已获得 Mutex2,等待 3 秒后释放...");
Thread.Sleep(3000);
mutex2.ReleaseMutex();
Console.WriteLine("线程2 已释放 Mutex2。");
}
}
SignalAndWait
用于占用一该资源之后,释放的同时又获取另外一种资源。
using System;
using System.Threading;
class Program
{
private static Mutex mutex1 = new Mutex();
private static Mutex mutex2 = new Mutex();
static void Main()
{
Thread thread1 = new Thread(ThreadProc1);
Thread thread2 = new Thread(ThreadProc2);
thread1.Start();
thread2.Start();
thread1.Join();
thread2.Join();
}
static void ThreadProc1()
{
mutex1.WaitOne();
Console.WriteLine("线程1获得了 mutex1");
// 释放 mutex1 并等待获取 mutex2
Mutex.SignalAndWait(mutex1, mutex2);
Console.WriteLine("线程1现在拥有了 mutex2");
}
static void ThreadProc2()
{
mutex2.WaitOne();
Console.WriteLine("线程2获得了 mutex2");
// 释放 mutex2 并等待获取 mutex1
Mutex.SignalAndWait(mutex2, mutex1);
Console.WriteLine("线程2现在拥有了 mutex1");
}
}
Close
关闭此Mutex实例持有的所有资源。
using System;
using System.Threading;
class Program
{
static void Main()
{
Mutex mutex = new Mutex(false, "MyMutex");
// 使用 Mutex 进行一些操作
// ...
// 显式关闭 Mutex
mutex.Close();
}
}
具体例子:
在使用Mutex之后,后面关闭它,此时线程试图获取它或者做任何操作将会报错。
using System;
using System.Threading;
class Program
{
static void Main()
{
// 创建两个 Mutex 对象
Mutex mutex1 = new Mutex();
Mutex mutex2 = new Mutex();
// 线程1尝试获得第一个 Mutex
Thread thread1 = new Thread(() =>
{
Console.WriteLine("线程1 尝试获取 Mutex1...");
mutex1.WaitOne();
Console.WriteLine("线程1 已获取 Mutex1。");
Thread.Sleep(2000); // 模拟一些工作
Console.WriteLine("线程1 释放 Mutex1。");
mutex1.ReleaseMutex();
});
// 线程2尝试获得第二个 Mutex
Thread thread2 = new Thread(() =>
{
Console.WriteLine("线程2 尝试获取 Mutex2...");
mutex2.WaitOne();
Console.WriteLine("线程2 已获取 Mutex2。");
Thread.Sleep(1000); // 模拟一些工作
Console.WriteLine("线程2 释放 Mutex2。");
mutex2.ReleaseMutex();
});
thread1.Start();
thread2.Start();
thread1.Join();
thread2.Join();
// 关闭 Mutex 对象
mutex1.Close();
mutex2.Close();
Console.WriteLine("Mutex 对象已关闭。");
}
}
此方法使用后,已经具有Mutex的代码不会停止运行,将会继续执行操作,只是不能再获得它。
到这你已经学会了怎么使用三种方法实现同步,可以的话点个关注支持一下,谢谢你奥。
标签:...,13,Console,Monitor,Thread,C#,Mutex,WriteLine,线程 From: https://blog.csdn.net/m0_54138660/article/details/142184597