首页 > 编程语言 >C#笔记13 线程同步概念及其实现,详解lock,Monitor,Mutex代码用法

C#笔记13 线程同步概念及其实现,详解lock,Monitor,Mutex代码用法

时间:2024-09-13 23:50:42浏览次数:12  
标签:... 13 Console Monitor Thread C# Mutex WriteLine 线程

同步的概念

在我们学会在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

相关文章

  • C语言 ——— 学习并使用 #if …… #endif 条件编译指令
    目录何为条件编译指令常见的条件编译指令学习条件编译指令使用条件编译指令在程序预编译阶段,条件编译指令的代码转换多分支的条件编译指令何为条件编译指令在编译一个程序的时候,如果要将一条语句(一组语句)选择编译或者放弃时,就使用条件编译指令常见的条件编译指令#i......
  • C语言 ——— 条件编译指令实际用途
    目录前言头文件被包含的方式嵌套文件包含使用条件编译指令规避头文件多次包含 还有一个编译指令,同样能做到以上功能 前言条件编译指令多用于对头文件的定义和判断以及删除头文件被包含的方式本地文件包含(也就是自己创建的头文件):#include"stdio.h"本地文件包......
  • C++一元多项式解析、计算、输出(数据结构作业),可直接运行
    //Copyright(c)[email protected]#include<bits/stdc++.h>classPolynomial{private:std::unordered_map<int,int>data_;voidzero_value_optimization(){for(autoiter=data_.begin();iter!=data_.end();){......
  • [题解]CF542A Place Your Ad Here
    思路首先因为电视台比广告多一个信息,所以通常来说枚举电视台是更有前途的。因此枚举每一个电视台,考虑所有广告的贡献。对于一个电视台,\(c_i\)是定值,也就是找到所有广告与电视台所表示区间交得最多的那一个。假设枚举的电视台控制了\([L,R]\)区间,则广告\([l,r]\)会有三种方......
  • WPF Datagrid DataGridTemplateColumn.CellTemplate local:ImageTextblock ImgUrl="{
    DataGridTemplate.CellTemplatecontainsonedatatemplaewilldisplaythecustomoizedcontrol,thekeylocatedatthecustomcontrol'sdependencypropertybindingandrelativesourceofx:typedatagridrow<local:ImageTextblockImgUrl="{Binding......
  • 【随想录day2】LeetCode209长度最小的子数组 | LeetCode59螺旋矩阵
    LeetCode209长度最小的子数组1、题目:给定一个含有n个正整数的数组和一个正整数s,找出该数组中满足其和≥s的长度最小的连续子数组,并返回其长度。如果不存在符合条件的子数组,返回0示例:输入:s=7,nums=[2,3,1,2,4,3]输出:2解释:子数组[4,3]是该条件下的长度最小......
  • 学习日历 -2024/9/13
    从今天开始放中秋假期,5天的时间,实在是太棒了建民说下周四要补测,还好不是周五,周五周六我要出去今天学习了数据结构二叉树的一些基本知识数据结构(树)度:每一个节点的字节点数量树高:树的总层数根结点:最顶层的节点左子节点:左下方的节点右子节点:右下方的节点根结点......
  • AV1 Bitstream & Decoding Process Specification--[4]:语法结构
    原文地址:https://aomediacodec.github.io/av1-spec/av1-spec.pdf没有梯子的下载地址:AV1Bitstream&DecodingProcessSpecification摘要:这份文档定义了开放媒体联盟(AllianceforOpenMedia)AV1视频编解码器的比特流格式和解码过程。规范:此文档规定了开放媒体联盟(Alliance......
  • CSS基本布局理解——WEB开发系列38
    对CSS学习已经接近尾声,下面你可以对以下两道“小卡拉米”测试进行测试下CSS理解程度。题1:基于栅格布局的现代博客首页设计题目要求:创建一个博客首页布局,包含一个顶部导航栏、一个主要的内容区域(左侧为博客文章列表,右侧为一个侧边栏显示推荐内容),以及一个底部的页脚。要求......
  • Cilium网络插件
    一、基础知识:eBPF和XDP1.1BPF全称为“BerkeleyPacketFilter”,于1997年自Linux2.1.75版本的内核引入。基于寄存器(CPU之上的小型存储空间)的虚拟机,运行于内核空间。主要功能包括:负责运行从用户空间(通过系统调用)注入的代码而无须对内核进行编程(开发内核模块)。使用自定义的64......