.NET怎么实现多线程编程中的线程同步

来源:APP编程网作者:灯下变量头衔:程序员
导读:本期聚焦于小伙伴创作的《.NET怎么实现多线程编程中的线程同步》,敬请观看详情,探索知识的价值。以下视频、文章将为您系统阐述其核心内容与价值。如果您觉得《.NET怎么实现多线程编程中的线程同步》有用,将其分享出去将是对创作者最好的鼓励。

在.NET多线程编程中,当多个线程同时操作同一个共享资源时,很容易出现竞态条件,导致数据错乱、业务逻辑异常等问题,线程同步就是用来协调多个线程对共享资源的访问顺序,保证同一时间只有一个或指定数量的线程能操作共享资源的技术。

.NET怎么实现多线程编程中的线程同步

为什么需要线程同步

假设我们有一个共享的计数器变量,两个线程同时执行加1操作,正常情况下两次加1后结果应该是2,但如果没有同步机制,可能出现两个线程同时读取到初始值0,各自加1后都写回1,最终结果不符合预期。这种问题在并发量高的时候会更难排查,所以线程同步是多线程开发中的必备技能。

常用的.NET线程同步方案

1. lock语句

lock是.NET中最常用的轻量级同步方式,它本质是Monitor类的语法糖,会保证同一时间只有一个线程能进入lock包裹的代码块。lock的对象必须是引用类型,且推荐使用私有的只读对象,避免使用公共对象或值类型。

using System;
using System.Threading;

class Counter
{
    // 私有只读的锁对象,避免外部修改
    private readonly object _lockObj = new object();
    private int _count = 0;

    public void Increment()
    {
        // lock保证同一时间只有一个线程能执行下面的代码
        lock (_lockObj)
        {
            int temp = _count;
            // 模拟耗时操作
            Thread.Sleep(10);
            _count = temp + 1;
        }
    }

    public int GetCount()
    {
        return _count;
    }
}

class Program
{
    static void Main()
    {
        Counter counter = new Counter();
        // 创建两个线程同时执行加1操作
        Thread t1 = new Thread(() => 
        {
            for (int i = 0; i < 100; i++)
            {
                counter.Increment();
            }
        });
        Thread t2 = new Thread(() => 
        {
            for (int i = 0; i < 100; i++)
            {
                counter.Increment();
            }
        });

        t1.Start();
        t2.Start();
        t1.Join();
        t2.Join();

        Console.WriteLine($"最终计数器值:{counter.GetCount()}");
    }
}

上面的代码中,两个线程各执行100次加1操作,使用lock后最终输出结果一定是200,如果没有lock包裹,结果大概率会小于200。

2. Monitor类

lock语句是Monitor的简化写法,Monitor类提供了更灵活的同步控制,支持尝试进入临界区、设置等待超时等能力。常用的核心方法有Monitor.EnterMonitor.ExitMonitor.TryEnter

using System;
using System.Threading;

class MonitorDemo
{
    private readonly object _lockObj = new object();
    private int _sharedData = 0;

    public void UpdateData()
    {
        // 尝试进入临界区,最多等待500毫秒
        if (Monitor.TryEnter(_lockObj, 500))
        {
            try
            {
                int temp = _sharedData;
                Thread.Sleep(20);
                _sharedData = temp + 1;
                Console.WriteLine($"线程{Thread.CurrentThread.ManagedThreadId}更新数据为{_sharedData}");
            }
            finally
            {
                // 必须手动释放锁,否则会导致死锁
                Monitor.Exit(_lockObj);
            }
        }
        else
        {
            Console.WriteLine($"线程{Thread.CurrentThread.ManagedThreadId}等待超时,未获取到锁");
        }
    }
}

class Program
{
    static void Main()
    {
        MonitorDemo demo = new MonitorDemo();
        for (int i = 0; i < 5; i++)
        {
            Thread thread = new Thread(demo.UpdateData);
            thread.Start();
        }
    }
}

Monitor.TryEnter可以在获取不到锁的时候不阻塞线程,而是执行其他逻辑,适合对响应时间有要求的场景。

3. Mutex类

Mutex(互斥体)和lock、Monitor的作用类似,但是Mutex支持跨进程同步,而lock和Monitor只能用于同一进程内的线程同步。Mutex分为本地Mutex和命名Mutex,命名Mutex可以用于不同进程之间的同步。

using System;
using System.Threading;

class MutexDemo
{
    // 创建命名Mutex,用于跨进程同步
    private static Mutex _mutex = new Mutex(false, "Global\MyMutex");

    public static void DoWork()
    {
        // 等待获取Mutex,无限等待
        _mutex.WaitOne();
        try
        {
            Console.WriteLine($"进程{Environment.ProcessId}的线程{Thread.CurrentThread.ManagedThreadId}进入临界区");
            Thread.Sleep(1000);
            Console.WriteLine($"进程{Environment.ProcessId}的线程{Thread.CurrentThread.ManagedThreadId}离开临界区");
        }
        finally
        {
            // 释放Mutex
            _mutex.ReleaseMutex();
        }
    }
}

class Program
{
    static void Main()
    {
        MutexDemo.DoWork();
    }
}

如果需要多个进程访问同一个共享资源,比如操作同一个文件,就可以使用命名Mutex来做同步。

4. Semaphore类

Semaphore(信号量)可以限制同时访问共享资源的线程数量,比如某个资源最多允许3个线程同时访问,就可以用Semaphore来实现。Semaphore同样支持跨进程使用,对应的轻量级版本是SemaphoreSlim,只能用于同一进程内。

using System;
using System.Threading;

class SemaphoreDemo
{
    // 初始化信号量,最多允许2个线程同时访问
    private static SemaphoreSlim _semaphore = new SemaphoreSlim(2, 2);

    public static void AccessResource()
    {
        Console.WriteLine($"线程{Thread.CurrentThread.ManagedThreadId}等待进入资源区");
        // 等待获取信号量
        _semaphore.Wait();
        try
        {
            Console.WriteLine($"线程{Thread.CurrentThread.ManagedThreadId}进入资源区");
            Thread.Sleep(1000);
            Console.WriteLine($"线程{Thread.CurrentThread.ManagedThreadId}离开资源区");
        }
        finally
        {
            // 释放信号量
            _semaphore.Release();
        }
    }
}

class Program
{
    static void Main()
    {
        for (int i = 0; i < 5; i++)
        {
            Thread thread = new Thread(SemaphoreDemo.AccessResource);
            thread.Start();
        }
    }
}

上面的代码中,信号量初始计数为2,所以同时最多有2个线程能进入资源区,其他线程需要等待前面的线程释放信号量后才能进入。

5. 事件等待句柄

.NET中的事件等待句柄包括AutoResetEvent、ManualResetEvent、ManualResetEventSlim,用于线程之间的通知和等待,比如一个线程完成某项工作后通知其他等待的线程继续执行。

AutoResetEvent在收到信号后会自动重置为无信号状态,每次只能唤醒一个等待线程;ManualResetEvent需要手动重置状态,收到信号后会保持有信号状态,所有等待的线程都会被唤醒。

using System;
using System.Threading;

class EventDemo
{
    // 初始化为无信号状态
    private static AutoResetEvent _autoEvent = new AutoResetEvent(false);

    public static void WorkerThread()
    {
        Console.WriteLine($"线程{Thread.CurrentThread.ManagedThreadId}开始等待信号");
        // 等待信号
        _autoEvent.WaitOne();
        Console.WriteLine($"线程{Thread.CurrentThread.ManagedThreadId}收到信号,继续执行");
    }

    public static void SignalThread()
    {
        Thread.Sleep(2000);
        Console.WriteLine("主线程发送信号");
        // 发送信号,唤醒一个等待的线程
        _autoEvent.Set();
    }
}

class Program
{
    static void Main()
    {
        for (int i = 0; i < 3; i++)
        {
            Thread worker = new Thread(EventDemo.WorkerThread);
            worker.Start();
        }
        Thread signalThread = new Thread(EventDemo.SignalThread);
        signalThread.Start();
    }
}

不同同步方案的选择建议

如果只是同一进程内简单的临界区保护,优先使用lock语句,写法简单且不容易出错;如果需要更灵活的控制比如超时等待,可以使用Monitor类;如果需要跨进程同步,选择Mutex;如果需要限制并发访问的线程数量,选择Semaphore或SemaphoreSlim;如果需要线程之间的通知等待,选择事件等待句柄。同时要注意避免死锁问题,比如不要嵌套获取多个锁,或者按照固定的顺序获取锁。

C#_thread_synchronizationlock_statementMonitor_classSemaphore_class修改时间:2026-06-18 00:06:34

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。