浅谈.NET下的多线程和并行计算(十三)CLR via C#第三版阅读笔记(2)

简介: 线程同步的问题 1) 我们必须找到代码中所有可能被多个线程同时访问的资源,然后使用线程同步来保护资源,并且我们没有办法来验证是不是正确进行了线程同步,包括是否有遗漏和是否对不需要同步的资源进行同步。 2) 线程同步是有损性能的,如果某个操作大量执行,并且这个操作原先的执行时间非常短,那么如果我们对这段操作前后进行锁的申请和释放的话性能可能下降一个数量级。

线程同步的问题

1) 我们必须找到代码中所有可能被多个线程同时访问的资源,然后使用线程同步来保护资源,并且我们没有办法来验证是不是正确进行了线程同步,包括是否有遗漏和是否对不需要同步的资源进行同步。

2) 线程同步是有损性能的,如果某个操作大量执行,并且这个操作原先的执行时间非常短,那么如果我们对这段操作前后进行锁的申请和释放的话性能可能下降一个数量级。

3) 线程同步可能导致更频繁的线程创建和上下文的切换。

当然,只有在下面的情况下才需要线程同步,换句话说我们尽量不要使用下面的方案来导致线程同步:

1) 只有写操作, 如果只是访问只读对象,即使多个线程同时访问也不会改变资源,因此不需要同步,当然如果有写操作的话,即使是读操作也要考虑是不是需要同步。

2) 引用类型或者是可变的类型,如果访问的都是值类型(这里不说比如寄存器缓存的特殊情况)或者说类型是不可变的(比如string),那么我们对类型的获取和改写都是基于新的类型而不是多个线程的共享资源,因此不需要同步。

3) static静态资源,这也就是资源访问域的问题,如果这个资源并不会被多个线程访问到,也就是这个资源属于每个线程本身的,那么当然不需要同步。

最后,微软确保FCL所有静态方法线程安全,但由于性能问题FCL不保证所有实例方法线程安全,我们需要自己实现线程同步。对于我们自己的类库最好也遵从这个标准。

 

两种同步结构

1) 用户模式,硬件提供支持,速度非常快,但是在block的时候消耗CPU资源,在未争用的时候不损失性能

2) 内核模式,操作系统提供支持,速度比较慢,但是很灵活(比如可以设定超时时间,可以等待一组同步结构都可用的时候继续)并且和用户模式想法的是在block的时候可以不消耗CPU

作者进行了一个性能测试:

class Program
{
    static void Main(string[] args)
    {
        Int32 x = 0;
        const Int32 iterations = 5000000;
        Stopwatch sw = Stopwatch.StartNew();

        SimpleSpinLock ssl = new SimpleSpinLock();
        for (Int32 i = 0; i < iterations; i++)
        {
            ssl.Enter(); x++; ssl.Leave();
        }
        Console.WriteLine("Incrementing x in SimpleSpinLock: {0:N0}", sw.ElapsedMilliseconds);
        using (SimpleWaitLock swl = new SimpleWaitLock())
        {
            sw = Stopwatch.StartNew();
            for (Int32 i = 0; i < iterations; i++)
            {
                swl.Enter(); x++; swl.Leave();
            }
            Console.WriteLine("Incrementing x in SimpleWaitLock: {0:N0}", sw.ElapsedMilliseconds);
        }
    }
}

struct SimpleSpinLock
{
    private Int32 m_ResourceInUse;
    public void Enter()
    {
        while (Interlocked.Exchange(ref m_ResourceInUse, 1) != 0)
        {
        }
    }
    public void Leave()
    {
        Thread.VolatileWrite(ref m_ResourceInUse, 0);
    }
}

class SimpleWaitLock : IDisposable
{
    private AutoResetEvent m_ResourceFree = new AutoResetEvent(true);
    public void Enter()
    {
        m_ResourceFree.WaitOne();
    }
    public void Leave()
    {
        m_ResourceFree.Set();
    }
    public void Dispose() 
    {
        m_ResourceFree.Close();
    }
}

结果表明基于内核模式的同步比基于用户模式的同步慢了数十倍:

image

有关内核模式的各种结构我们在之前的文章中介绍了很多,在这里再补充一下两种用户模式的同步结构:

1) Volatile

两个作用一是防止编译器JIT甚至CPU对代码和指令等进行优化(比如跳过判断,调整代码次序),二是防止多核CPU分别在自己的寄存器中缓存了值导致的不一致性,让值始终从内存中读取。

2) Interlocked

Interlocked.Exchange和Interlocked.CompareExchange分别用于确保赋值操作和比较赋值操作的原子性,值得一提的是,Interlocked.Exchange并没有提供bool重载,我们可以使用int的0和1替代。还有,Interlocked.Read只提供了long重载,那是因为读一个long值的任何操作在32位操作系统上不是原子行为,而在64位操作系统上long的读操作是原子的。

作者: lovecindywang
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
相关文章
|
Python
解锁Python并发新世界:线程与进程的并行艺术,让你的应用性能翻倍!
【7月更文挑战第9天】并发编程**是同时执行多个任务的技术,提升程序效率。Python的**threading**模块支持多线程,适合IO密集型任务,但受GIL限制。**multiprocessing**模块允许多进程并行,绕过GIL,适用于CPU密集型任务。例如,计算平方和,多线程版本使用`threading`分割工作并同步结果;多进程版本利用`multiprocessing.Pool`分块计算再合并。正确选择能优化应用性能。
151 1
|
10月前
|
Java 调度
【源码】【Java并发】【线程池】邀请您从0-1阅读ThreadPoolExecutor源码
当我们创建一个`ThreadPoolExecutor`的时候,你是否会好奇🤔,它到底发生了什么?比如:我传的拒绝策略、线程工厂是啥时候被使用的? 核心线程数是个啥?最大线程数和它又有什么关系?线程池,它是怎么调度,我们传入的线程?...不要着急,小手手点上关注、点赞、收藏。主播马上从源码的角度带你们探索神秘线程池的世界...
508 0
【源码】【Java并发】【线程池】邀请您从0-1阅读ThreadPoolExecutor源码
|
存储 设计模式 分布式计算
Java中的多线程编程:并发与并行的深度解析####
在当今软件开发领域,多线程编程已成为提升应用性能、响应速度及资源利用率的关键手段之一。本文将深入探讨Java平台上的多线程机制,从基础概念到高级应用,全面解析并发与并行编程的核心理念、实现方式及其在实际项目中的应用策略。不同于常规摘要的简洁概述,本文旨在通过详尽的技术剖析,为读者构建一个系统化的多线程知识框架,辅以生动实例,让抽象概念具体化,复杂问题简单化。 ####
|
SQL 传感器 开发框架
今天我们聊聊C#的并发和并行
今天我们聊聊C#的并发和并行
250 2
|
并行计算 安全 Java
Python 多线程并行执行详解
Python 多线程并行执行详解
537 3
|
算法 Java
JUC(1)线程和进程、并发和并行、线程的状态、lock锁、生产者和消费者问题
该博客文章综合介绍了Java并发编程的基础知识,包括线程与进程的区别、并发与并行的概念、线程的生命周期状态、`sleep`与`wait`方法的差异、`Lock`接口及其实现类与`synchronized`关键字的对比,以及生产者和消费者问题的解决方案和使用`Condition`对象替代`synchronized`关键字的方法。
JUC(1)线程和进程、并发和并行、线程的状态、lock锁、生产者和消费者问题
|
SQL 开发框架 安全
并发集合与任务并行库:C#中的高效编程实践
在现代软件开发中,多核处理器普及使多线程编程成为提升性能的关键。然而,传统同步模型在高并发下易引发死锁等问题。为此,.NET Framework引入了任务并行库(TPL)和并发集合,简化并发编程并增强代码可维护性。并发集合允许多线程安全访问,如`ConcurrentQueue&lt;T&gt;`和`ConcurrentDictionary&lt;TKey, TValue&gt;`,有效避免数据不一致。TPL则通过`Task`类实现异步操作,提高开发效率。正确使用这些工具可显著提升程序性能,但也需注意任务取消和异常处理等常见问题。
221 1
|
JSON 开发框架 JavaScript
【Azure Developer】使用.Net Core解析JSON的笔记
【Azure Developer】使用.Net Core解析JSON的笔记
155 1
|
SQL 存储 监控
SQLServer事务复制延迟优化之并行(多线程)复制
【9月更文挑战第12天】在SQL Server中,事务复制延迟会影响数据同步性。并行复制可通过多线程处理优化这一问题,提高复制效率。主要优化方法包括:配置分发代理参数、优化网络带宽、调整系统资源、优化数据库设计及定期监控维护。合理实施这些措施可提升数据同步的及时性和可靠性。
503 0
|
开发框架 监控 .NET

热门文章

最新文章