惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

B
Blog RSS Feed
V2EX - 技术
V2EX - 技术
P
Privacy & Cybersecurity Law Blog
T
The Exploit Database - CXSecurity.com
美团技术团队
WordPress大学
WordPress大学
博客园 - 司徒正美
S
Securelist
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
博客园 - Franky
Attack and Defense Labs
Attack and Defense Labs
Security Latest
Security Latest
L
LINUX DO - 最新话题
NISL@THU
NISL@THU
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
腾讯CDC
Y
Y Combinator Blog
The Hacker News
The Hacker News
Security Archives - TechRepublic
Security Archives - TechRepublic
IT之家
IT之家
T
Threatpost
Hugging Face - Blog
Hugging Face - Blog
Scott Helme
Scott Helme
S
SegmentFault 最新的问题
Cyberwarzone
Cyberwarzone
C
Cisco Blogs
阮一峰的网络日志
阮一峰的网络日志
U
Unit 42
B
Blog
Microsoft Azure Blog
Microsoft Azure Blog
P
Proofpoint News Feed
小众软件
小众软件
V
Vulnerabilities – Threatpost
J
Java Code Geeks
V
Visual Studio Blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
A
Arctic Wolf
博客园 - 【当耐特】
Microsoft Security Blog
Microsoft Security Blog
S
Security @ Cisco Blogs
雷峰网
雷峰网
Help Net Security
Help Net Security
The Last Watchdog
The Last Watchdog
Recent Announcements
Recent Announcements
G
Google Developers Blog
C
CERT Recently Published Vulnerability Notes
T
Troy Hunt's Blog
MyScale Blog
MyScale Blog

博客园 - 秃头星AI

如果没有 Tools,Agent 什么都做不了 为什么 Agent 一定要“思考→行动→观察”?聊聊 ReAct 循环 当大模型开始控制设备:我是怎么理解 Agent 架构的 AI 真正的突破,不是更聪明,而是开始能「行动」 Prompt 一旦超出上下文窗口,该怎么办? 别再把 RAG 当搜索:它本质上是在重构 Context 你以为AI记住了你,其实没有:大模型记忆机制拆解 看见,才会回答:Context 如何重塑 AI 的能力边界 你以为在给 AI 下命令,其实你在改写它的概率分布 AI真的会思考吗?当我们拆开大语言模型,看到的是一个完全反直觉的真相 三、控制权之争:从 Workflow 到 Claude Skills,AI 正在进入「执行契约时代」 二、LLM进化史:从“复读机”到“牛马员工”,Agent到底是个什么鬼? 一、大语言模型上班记:从只会接话到会查资料的进化史 .Net 使用 Consul Consul Windows部署 C#/C++ 通过ODBC连接OceanBase Oracle租户 线程同步构造 C++调用C# (使用C++/CLI) C#调用C++ (使用C++/CLI)
C# lock 和 Monitor
秃头星AI · 2024-04-07 · via 博客园 - 秃头星AI
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);
        }
    }
}

缺点

  1. 性能开销
    • Monitor 是一种内核模式构造,它提供了互斥锁功能,用于保护共享资源免受并发访问的影响。
    • 但是,每次在 Monitor 上调用 Enter Exit 方法时,都需要进行用户模式到内核模式的切换,这会导致性能开销。
    • 如果应用程序中频繁使用 Monitor,这些切换可能会显著影响整体性能。
  1. 死锁风险
    • Monitor 的使用需要谨慎,因为不正确的使用可能导致死锁。
    • 如果线程在持有锁的情况下等待其他资源,而其他资源又在等待相同的锁,就会发生死锁。
    • 死锁是一种严重的问题,会导致应用程序停滞不前。