C#多线程开发-线程同步 02

简介: C#多线程开发-线程同步 02

上一篇文章主要带领大家认识了线程,也了解到了线程的基本用法和状态,接下来就让我们一起学习下什么是线程同步。


线程中异常的处理


在线程中始终使用try/catch代码块是非常重要的,因为不可能在线程代码之外来捕获到异常。



可以阅读下面的代码,这块是做的验证,证明在线程之外捕获异常是错误的选择,应该在线程中时时刻刻都使用异常处理机制。


   static void Main(string[] args)
        {
            Thread twoThread = new Thread(TwoMethod);
            twoThread.Start();
            twoThread.Join();            
            try
            {
                Thread oneThread = new Thread(OneMethod);
                oneThread.Start();
            }
            catch (Exception ex)
            {
                Console.WriteLine("外部捕获线程one的异常:"+ex.Message);
            }            
            Console.ReadKey();
        }
        static void OneMethod() 
        {
            Console.WriteLine("Start OneMethod");
            Thread.Sleep(TimeSpan.FromSeconds(2));
            throw new Exception("异常01");
        }
        static void TwoMethod() 
        {
            try
            {
                Console.WriteLine("Start TwoMethod");
                Thread.Sleep(TimeSpan.FromSeconds(1));
                throw new Exception("异常02");
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);                
            }
        }


下面图片是输出报错的结果,可以看到OneThread的异常没有被外部的try/catch捕获到,导致直接在线程内部提示错误,导致程序崩溃。



看到这个情况,那么我们在以后使用线程的时候,就需要特别的注意,一定要在线程中进行异常的处理和捕获,千万别遗留任何未处理的异常,因为如果线程中有未被处理的异常会导致整个程序都会受到影响,可能导致整个软件崩溃。


线程同步



在上一篇推文中,我们了解到了Lock加锁的机制,它是可以保证将某个变量或者某个模块锁住,当出现多个线程同时访问时锁就会起作用,只允许一个线程访问,其余的等待,其访问完后其余的才可以进行访问。


但是这种机制有一定的局限性,在多核CPU设备中,让其余线程等待是极大浪费资源的,而且这种解决办法会导致死锁的现象。



上面说的也就是所谓的竞争条件问题的解决方法,导致这个问题的原因是多线程的执行并没有正确同步。当一个线程执行递增和递减操作时,其他线程需要依次等待。这种常见问题通常被称为线程同步。这种问题出现在当线程中有共享资源或者对象时,才会进行线程同步,如果无共享对象,则无需进行线程同步。


当在线程中有共享资源时,可使用下面的两种方式进行处理。


一、原子操作


来实现对共享资源的访问。其实就是一个操作只占用一个量子的时间,一次就可以完成。也就是说只有当前操作完成后,其他线程才能执行其他操作。这样就避免了使用锁,排除了死锁的情况。


原子操作就是使用C#系统自带的Interlocked类来对线程不安全的对象进行处理,借助Interlocked类,无需锁定任何对象即可获取到正确的结果,Interlocked类提供Increment,Decrement和Add等基本数学操作的原子方法,从而可以帮助我们无需使用锁🔒,避免出现各种死锁问题。


/// <summary>
    /// 线程不安全
    /// </summary>
    class Counter
    {
        private int _count;
        public int Count { get { return _count; } }
        public void Add() 
        {
            _count++;
        }
        public void Delete() 
        {
            _count--;
        }
    }


 /// <summary>
    /// 不加锁 ,但线程是安全的。
    /// 可避免在线程中出现死锁
    /// </summary>
    class CounterNoLock 
    {
        private int _count;
        public int Count { get { return _count; } }
        public void Add()
        {
            Interlocked.Increment(ref _count);          //递增
        }
        public void Delete()
        {
            Interlocked.Decrement(ref _count);          //递减
        }
    }


二、将等待的线程置于阻塞状态


这个处理方法是在第一个原子操作无效切程序的逻辑更加复杂的情况下才使用的,用于协调线程。


当线程处理阻塞状态时,只会占用尽可能少的CPU时间,这就意味着将引入至少一次所谓的上下文切换


上下文切换:指操作系统的线程调度器,该调度器会保持等待的线程的状态,并切换到另一个线程,依次恢复等待的线程状态。虽然会消耗极大的资源,但是如果线程被挂起很长时间这么做是值得的。这种也就内核模式,因为只有操作系统的内核才能阻止线程使用CPU时间。


用户模式: 如果线程只是等候一小会,那最好只是简单的等待,而不用将线程切换到阻塞状态。还有一种为混合模式,也就是先尝试使用用户模式,如果线程等候时间过长,则会切换到阻塞状态以节省CPU资源。


下面的DEMO主要介绍SemaphoreSlim类,该类用于限制了同时访问同一个资源的线程数量。


     static void Main(string[] args)
        {   
            for (int i = 1; i <=6; i++)
            {
                string threadName = "Thread " + i;
                int secondsWait = 2;
                var thread = new Thread(( )=>DataConnect(threadName,secondsWait));
                thread.Start();
            }
            Console.ReadKey();
        }
        static SemaphoreSlim _semaphore = new SemaphoreSlim(4);             //默认4个线程可同时访问
        static void DataConnect(string name,int seconds) 
        {
            Console.WriteLine("wait 线程的名字:",name);
            _semaphore.Wait();
            Console.WriteLine("Connect 线程的名字:" + name);
            Thread.Sleep(TimeSpan.FromSeconds(seconds));
            _semaphore.Release();
        }


上面的代码利用SemaphoreSlim类,设置其构造函数为4,也就是其指定允许的并发线程数量。


上面使用信号系统限制了访问数据连接的并发数为4个,当有4个线程进行访问时,其他两个线程需要等待,知道之前线程中某一个完成工作并调用Relece方法来发出信号。


目录
相关文章
|
10天前
|
前端开发 JavaScript 安全
C#一分钟浅谈:Blazor WebAssembly 开发
Blazor WebAssembly 是一个客户端框架,允许开发者使用C#和Razor语法构建Web应用。本文介绍了Blazor WebAssembly的基本概念、常见问题及解决方案,包括路由配置、数据绑定、异步操作、状态管理和性能优化等方面的内容,并分享了一些易错点及如何避免的方法。希望这些内容能帮助你在Blazor WebAssembly开发中少走弯路,提高开发效率。
84 51
|
8天前
|
开发框架 缓存 .NET
C# 一分钟浅谈:Blazor Server 端开发
Blazor Server 是基于 ASP.NET Core 的框架,允许使用 C# 和 Razor 语法构建交互式 Web 应用。本文介绍 Blazor Server 的基本概念、快速入门、常见问题及解决方案,帮助开发者快速上手。涵盖创建应用、基本组件、数据绑定、状态管理、跨组件通信、错误处理和性能优化等内容。
21 1
|
9天前
|
缓存 C# 开发者
C# 一分钟浅谈:Blazor Server 端开发
本文介绍了 Blazor Server,一种基于 .NET 的 Web 开发模型,允许使用 C# 和 Razor 语法构建交互式 Web 应用。文章从基础概念、创建应用、常见问题及解决方案、易错点及避免方法等方面详细讲解,帮助开发者快速上手并提高开发效率。
30 2
|
18天前
|
测试技术 Go C#
C#一分钟浅谈:ReSharper 插件增强开发效率
【10月更文挑战第25天】ReSharper 是 JetBrains 开发的一款 Visual Studio 插件,旨在提高 .NET 开发者的生产力。它通过代码分析、重构、导航等功能,帮助开发者避免常见错误,提升代码质量和开发效率。本文将通过具体代码案例,详细介绍 ReSharper 的常见功能及其应用。
35 1
|
24天前
|
C# Python
使用wxpython开发跨平台桌面应用,对wxpython控件实现类似C#扩展函数处理的探究
【10月更文挑战第30天】使用 `wxPython` 开发跨平台桌面应用时,可以通过创建辅助类来模拟 C# 扩展函数的功能。具体步骤包括:1. 创建辅助类 `WxWidgetHelpers`;2. 在该类中定义静态方法,如 `set_button_color`;3. 在应用中调用这些方法。这种方法提高了代码的可读性和可维护性,无需修改 `wxPython` 库即可为控件添加自定义功能。但需要注意显式调用方法和避免命名冲突。
|
26天前
|
Java 开发者
在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口
【10月更文挑战第20天】在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口。本文揭示了这两种方式的微妙差异和潜在陷阱,帮助你更好地理解和选择适合项目需求的线程创建方式。
19 3
|
26天前
|
Java 开发者
在Java多线程编程中,选择合适的线程创建方法至关重要
【10月更文挑战第20天】在Java多线程编程中,选择合适的线程创建方法至关重要。本文通过案例分析,探讨了继承Thread类和实现Runnable接口两种方法的优缺点及适用场景,帮助开发者做出明智的选择。
16 2
|
26天前
|
Java
Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口
【10月更文挑战第20天】《JAVA多线程深度解析:线程的创建之路》介绍了Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口。文章详细讲解了每种方式的实现方法、优缺点及适用场景,帮助读者更好地理解和掌握多线程编程技术,为复杂任务的高效处理奠定基础。
28 2
|
26天前
|
Java 开发者
Java多线程初学者指南:介绍通过继承Thread类与实现Runnable接口两种方式创建线程的方法及其优缺点
【10月更文挑战第20天】Java多线程初学者指南:介绍通过继承Thread类与实现Runnable接口两种方式创建线程的方法及其优缺点,重点解析为何实现Runnable接口更具灵活性、资源共享及易于管理的优势。
31 1
|
26天前
|
安全 Java 开发者
Java多线程中的`wait()`、`notify()`和`notifyAll()`方法,探讨了它们在实现线程间通信和同步中的关键作用
本文深入解析了Java多线程中的`wait()`、`notify()`和`notifyAll()`方法,探讨了它们在实现线程间通信和同步中的关键作用。通过示例代码展示了如何正确使用这些方法,并分享了最佳实践,帮助开发者避免常见陷阱,提高多线程程序的稳定性和效率。
34 1