首页 > 编程语言 >C# lock 和 Monitor

C# lock 和 Monitor

时间:2024-04-07 18:35:04浏览次数:14  
标签:Console Monitor Thread C# lock 线程 lockObject

lock

lock 关键字是 C# 中最常用的线程同步工具之一,它使得一段代码在同一时间只能被一个线程执行,以确保对共享资源的访问不会被其他线程干扰。

lock 关键字的语法如下:

lock (lockObject)
{
    // 要同步的代码块
}
  • lockObject:是一个对象实例,用于定义临界区域。多个线程在执行 lock 代码块时,如果它们尝试获取相同的 lockObject 锁,则只有一个线程能够成功获取锁,其他线程将被阻塞,直到锁被释放。
  • lock 关键字的优点是简单易用,编译器会自动处理锁的获取和释放,使得代码更加清晰和简洁。
  • lock 关键字的缺点是在锁的范围内不能发生阻塞,否则会导致死锁。此外,由于 lock 是语言级别的关键字,所以无法对它进行更细粒度的控制。

在 C# 中,lock 关键字实际上是对 Monitor 类的一种简化用法,它会被编译器转换成 Monitor 类的方法调用。当你使用 lock 关键字时,编译器会自动创建一个 Monitor 对象,并在代码块的开始处调用 Monitor.Enter 方法获取锁,在代码块的结束处调用 Monitor.Exit 方法释放锁。

Monitor

Monitor 类提供了细粒度的线程同步控制,它允许程序员手动管理同步块的进入和退出,并且支持更复杂的线程同步操作。

Monitor 类的基本用法如下:

// 进入同步代码块
Monitor.Enter(lockObject);
try
{
    // 同步代码块
}
finally
{
    // 退出同步代码块
    Monitor.Exit(lockObject);
}
  • Monitor 类需要使用 EnterExit 方法手动管理同步块的进入和退出。Enter 方法用于进入同步代码块,Exit 方法用于退出同步代码块。通常会在 try-finally 块中使用,以确保锁的释放。
  • Monitor 类还提供了 WaitPulsePulseAll 方法,用于线程之间的通信和同步。Wait 方法用于让线程等待,Pulse 方法用于唤醒等待的线程,PulseAll 方法用于唤醒所有等待的线程。
  • Monitor 类的优点是可以手动控制同步块的进入和退出,以及支持更复杂的线程同步操作,比如等待和唤醒线程。
  • Monitor 类的缺点是使用起来相对复杂,需要手动管理锁的获取和释放,容易出现遗漏或错误,导致死锁或其他线程同步问题。

案例

分享一个简单的火车票购票系统的例子,来说明  lock 关键字的使用方式以及与Monitor 类的对比。

假设有一个火车票购票系统,多个用户同时在线购票。我们希望在多线程环境中正确管理火车票的分配,避免出现超卖或者座位重复分配的情况。

使用lock

创建一个 TicketSystem 类来管理座位的状态和购票操作:

using System;
using System.Threading;

public class TicketSystem
{
    private object lockObject = new object(); // 用于同步的锁对象
    private int availableSeats; // 可用座位数量

    public TicketSystem(int totalSeats)
    {
        availableSeats = totalSeats;
    }

    public void BuyTicket(string customerName, int numSeats)
    {
        lock (lockObject)
        {
            if (availableSeats >= numSeats)
            {
                // 模拟购票操作
                Console.WriteLine($"{customerName} 购买了 {numSeats} 张票。");
                availableSeats -= numSeats;
            }
            else
            {
                Console.WriteLine($"对不起,{customerName},没有足够的座位。");
            }
        }
    }
}


class Program
{
    static void Main(string[] args)
    {
        TicketSystem ticketSystem = new TicketSystem(15); // 总共15个座位

        Thread[] threads = new Thread[20]; // 20个线程模拟20个用户

        for (int i = 0; i < 20; i++)
        {
            threads[i] = new Thread(() =>
            {
                string customerName = $"User {Thread.CurrentThread.ManagedThreadId}";
                ticketSystem.BuyTicket(customerName, 1);
            });
        }

        // 启动所有线程
        foreach (var thread in threads)
        {
            thread.Start();
        }

        // 等待所有线程执行完成
        foreach (var thread in threads)
        {
            thread.Join();
        }

        Console.WriteLine("所有用户购票结束。");
        Console.ReadKey();
    }
}

使用Monitor 

using System;
using System.Threading;

public class TicketSystem
{
    private object lockObject = new object(); // 用于同步的锁对象
    private int availableSeats; // 可用座位数量

    public TicketSystem(int totalSeats)
    {
        availableSeats = totalSeats;
    }

    public void BuyTicket(string customerName, int numSeats)
    {
        Monitor.Enter(lockObject);
        try
        {
            if (availableSeats >= numSeats)
            {
                // 模拟购票操作
                Console.WriteLine($"{customerName} 购买了 {numSeats} 张票。");
                availableSeats -= numSeats;
            }
            else
            {
                Console.WriteLine($"对不起,{customerName},没有足够的座位。");
            }
        }
        finally
        {
            Monitor.Exit(lockObject);
        }
    }
}

class Program
{
    static void Main(string[] args)
    {
        TicketSystem ticketSystem = new TicketSystem(15); // 总共15个座位

        Thread[] threads = new Thread[20]; // 20个线程模拟20个用户

        for (int i = 0; i < 20; i++)
        {
            threads[i] = new Thread(() =>
            {
                string customerName = $"User {Thread.CurrentThread.ManagedThreadId}";
                ticketSystem.BuyTicket(customerName, 1);
            });
        }

        // 启动所有线程
        foreach (var thread in threads)
        {
            thread.Start();
        }

        // 等待所有线程执行完成
        foreach (var thread in threads)
        {
            thread.Join();
        }

        Console.WriteLine("所有用户购票结束。");
        Console.ReadKey();
    }
}

上面的场景中使用lock和Moniter效果都是相同的,接下来描述lock 没有的功能。Monitor的Wait和Pulse 方法

Monitor的Wait和Pulse

我们知道春节火车票不是一次性放票的而是分段开放的,同时也有退票的情况。这将导致余票的数量动态变化。多个用户线程同时等待抢票,抢票系统一旦检测到有新的余票释放,将通知所有等待的线程进行抢票。这种情况下,我们需要使用线程同步机制确保多个线程之间的正确操作,并及时通知用户线程有新票可供抢购。

using System;
using System.Threading;

public class TicketSystem
{
    private int availableTickets = 0;
    private object lockObject = new object();

    public TicketSystem(int initialTickets)
    {
        this.availableTickets = initialTickets;
    }

    public void SellTicket()
    {
        Monitor.Enter(lockObject);
        try
        {
            while (availableTickets == 0)
            {
                Console.WriteLine($"{Thread.CurrentThread.Name} is waiting for available tickets...");
                Monitor.Wait(lockObject);
            }

            // Simulate selling ticket
            availableTickets--;
            Console.WriteLine($"{Thread.CurrentThread.Name} sold a ticket. Available tickets: {availableTickets}");
        }
        finally
        {
            Monitor.Exit(lockObject);
        }
    }

    public void AddTickets(int numTicketsToAdd)
    {
        Monitor.Enter(lockObject);
        try
        {
            // Simulate adding new tickets
            availableTickets += numTicketsToAdd;
            Console.WriteLine($"Added {numTicketsToAdd} new tickets. Available tickets: {availableTickets}");

            // Notify waiting threads
            Monitor.PulseAll(lockObject);
        }
        finally
        {
            Monitor.Exit(lockObject);
        }
    }
}

public class Program
{
    public static void Main(string[] args)
    {
        TicketSystem ticketSystem = new TicketSystem(0); // 初始没有票

        // 创建并启动5个抢票线程
        for (int i = 0; i < 5; i++)
        {
            Thread thread = new Thread(() =>
            {
                while (true)
                {
                    ticketSystem.SellTicket();
                    Thread.Sleep(1000); // 模拟抢票过程
                }
            });
            thread.Name = $"TicketBuyer {i + 1}";
            thread.Start();
        }

        // 模拟分段放票
        for (int i = 0; i < 3; i++)
        {
            Thread.Sleep(3000); // 等待3秒模拟间隔放票
            ticketSystem.AddTickets(5); // 每次放5张票
        }

        Console.WriteLine("Press any key to exit...");
        Console.ReadKey();
    }
}

这段代码实现了一个简单的抢票功能,具体功能和实现方式如下:

  • 使用 TicketSystem 类管理票务信息,包括余票数量和售票方法。
  • Main 方法中创建多个线程模拟不同的抢票者,它们会不断尝试购买票。
  • 使用 Monitor.EnterMonitor.Exit 实现线程同步,确保多线程访问临界区时的安全。
  • 使用 Monitor.WaitMonitor.PulseAll 实现线程间的通信,唤醒等待的线程。
  • 程序模拟分段开放票务,定时增加新的票。

由于 lock 示例本质上是实现了Monitor,所以也可以使用lock + Monitor.Wait 和 Monitor.PulseAll 方式使用。

public class TicketSystem
{
    private int availableTickets = 0;

    public TicketSystem(int initialTickets)
    {
        this.availableTickets = initialTickets;
    }

    public void SellTicket()
    {
        lock (this)
        {
            while (availableTickets == 0)
            {
                Console.WriteLine($"{Thread.CurrentThread.Name} is waiting for available tickets...");
                Monitor.Wait(this);
            }

            // Simulate selling ticket
            availableTickets--;
            Console.WriteLine($"{Thread.CurrentThread.Name} sold a ticket. Available tickets: {availableTickets}");
        }
    }

    public void AddTickets(int numTicketsToAdd)
    {
        lock (this)
        {
            // Simulate adding new tickets
            availableTickets += numTicketsToAdd;
            Console.WriteLine($"Added {numTicketsToAdd} new tickets. Available tickets: {availableTickets}");

            // Notify waiting threads
            Monitor.PulseAll(this);
        }
    }
}

 

标签:Console,Monitor,Thread,C#,lock,线程,lockObject
From: https://www.cnblogs.com/mchao/p/18119082

相关文章

  • 【27.0】RBAC权限系统
    【一】什么是RBAC【1】概念RBAC是基于角色的访问控制(Role-BasedAccessControl)在RBAC中,权限与角色相关联,用户通过成为适当角色的成员而得到这些角色的权限。这就极大地简化了权限的管理。这样管理都是层级相互依赖的,权限赋予给角色,而把角色又赋予用户,这样的权限设计很......
  • openGauss学习笔记-257 openGauss性能调优-使用Plan Hint进行调优-Custom Plan和Gener
    文章目录openGauss学习笔记-257openGauss性能调优-使用PlanHint进行调优-CustomPlan和GenericPlan选择的Hint257.1功能描述257.2语法格式257.3示例openGauss学习笔记-257openGauss性能调优-使用PlanHint进行调优-CustomPlan和GenericPlan选择的Hint257.......
  • mac 添加静态路由
    在macOS上,您可以使用`route`命令来添加一次性的静态路由。以下是配置一次性静态路由的一般步骤:1.**确定目标网络和下一跳地址:**首先,确定您要添加的静态路由的目标网络和下一跳地址。2.**使用routeadd命令添加静态路由:**打开终端,执行以下命令:```sudoroute-na......
  • C语言游戏实战(11):贪吃蛇大作战(多人对战)
         成果展示:贪吃蛇(多人对战) 前言:这款贪吃蛇大作战是一款多人游戏,玩家需要控制一条蛇在地图上移动,吞噬其他蛇或者食物来增大自己的蛇身长度和宽度。本游戏使用C语言和easyx图形库编写,旨在帮助初学者了解游戏开发的基本概念和技巧。在开始编写代码之前,我们需要先......
  • SPICE/SpiceyPy学习记录整理(一)-- SPICE概述
    目录一、SPICE概述二、SPICE内核文件三、SpiceToolkitSoftware介绍四、基础概念4.1前言4.2时间4.2.1概念4.2.2时间系统4.3参考系4.3.1参考系的种类4.3.2J2000坐标系与ICRF坐标系的对比4.4坐标系4.5状态4.5.1状态矢量4.5.2坐标转换4.6像差校正......
  • SPICE/SpiceyPy学习记录整理(三)-- Mice
    目录一、Mice介绍二、Mice使用方法1.1加载内核1.2卸载内核1.3矢量化参数三、示例SPICE官方课程学习文档链接:https://naif.jpl.nasa.gov/naif/tutorials.html一、Mice介绍        Mice是SPICE在MATLAB环境下的扩展工具包。所有Mice调用都是CSPICE函数,......
  • SPICE/SpiceyPy学习记录整理(二)-- 工具包安装与介绍
    目录一、获取、安装和引用SPICEToolkit1.1获取SPICEToolkit1.2安装SPICEToolkit1.3检查是否安装成功二、Toolkit介绍2.1工具包架构2.2MatlabToolkit--Mice 2.3Toolkit特点2.4工具包目录结构三、ToolkitLibrary介绍3.1 ToolkitLibrary概述3.2 Tool......
  • Android Studio学习14——认识Activity的启动模式
    默认情况下都是Standard模式借鉴other借鉴2......
  • C++初级----string类(STL)
    1、标准库中的string1.1、sring介绍    字符串是表示字符序列的类,标准的字符串类提供了对此类对象的支,其接口类似于标准字符容器的接口,但是添加了专门用于操作的单字节字符字符串的设计特性。    string类是使用char,即作为他的字符类型,使用他默认的char_tr......
  • csdn博客自定义模块:显示实时天气、日历、随机语录代码
    目录1.样式说明2.效果展示3.代码下载1.样式说明vip会员或者博客专家可以自定义模块代码,比如我博客的样式,有这几部分组成:灯笼祝福(我这里是龙年快乐,可以自定义更改任何字)、滚动欢迎语(我这里是欢迎访问我的博客,可以自定义更改任何欢迎语)github链接、知乎链接、邮箱发......