013.处理并发

这可能看起来并不明显,但代码隐藏了一个问题:并发性。StockTicker接口中没有任何内容承诺将引发tick事件的线程,也没有任何内容保证在StockMonitor处理另一个tick时不会引发tick,如图2.2所示。

在这里插入图片描述
图2.2多个线程同时执行事件处理程序代码。每个框表示一个股票的执行时间。当第一个线程运行MSFT的代码时,第二个线程开始执行GOOG股票。然后,第三个线程开始执行与第一个线程相同的库存符号。

您编写的StockMonitor类使用字典来保存有关股票的信息,但您使用的字典不是线程安全的。

什么是线程安全
代码部分的线程安全性意味着,当从多个线程调用代码时,无论这些线程执行代码的顺序如何,代码都能正常工作,并且不需要同步调用代码。
如果一个类的任何一个方法都是线程安全的,即使同时从不同的线程调用不同的方法,该类也被称为线程安全的。这通常意味着内部数据结构同时受到保护,不受修改。

您使用的词典确实同时支持多个访问者,但如果在修改词典时读取词典,则会引发异常。这种情况如表2.1所示。Thread1(在左边)到达标记的代码,在那里它试图获得带有符号symbol1的股票的StockInfo。同时,Thread2(右侧)到达将新的StockInfo(带有symbol2符号)添加到字典的代码行。读取和修改字典同时发生,并导致异常。

表2.1从两个线程同时读取和修改词典
在这里插入图片描述
你可以通过使用ConcurrentDictionary来克服这个问题。这个无锁集合在内部同步读取器和写入器,因此不会抛出异常。

不幸的是,ConcurrentDictionary是不够的,因为StockTicker没有同步tick。如果你同时处理同一只股票的两个(或多个)刻度,那么PrevPrice属性的价值是多少?这个问题有一个不确定的答案:最后一个获胜。但是,最后一个不一定是最后一个股票点,因为线程运行的顺序是由操作系统决定的,并且是不确定的。这会使您的代码不可靠,因为最终用户可能会收到您的代码得出的错误结论的通知。OnStockTick事件处理程序保存一个关键部分,保护它的方法是使用锁。

object _stockTickLocker = new object();//充当互斥锁的对象,您将在lock语句中使用该对象
void OnStockTick(object sender, StockTick stockTick)
{
    const decimal maxChangeRatio = 0.1m;
    StockInfo stockInfo;
    var quoteSymbol = stockTick.QuoteSymbol;
    
    //确保一个线程在另一个线程存在时不会进入代码的关键部分。
    //如果另一个线程试图输入一个锁定的代码,它将阻止,直到对象被释放。
    lock (_stockTickLocker)
    {
        var stockInfoExists =
        _stockInfos.TryGetValue(quoteSymbol, out stockInfo);
        if (stockInfoExists)
        {
            var priceDiff = stockTick.Price - stockInfo.PrevPrice;
            var changeRatio = Math.Abs(priceDiff / stockInfo.PrevPrice);
            if (changeRatio > maxChangeRatio)
            {
                Debug.WriteLine("Stock:{0} has changed with {1} ratio OldPrice:{ 2} newPrice: { 3} ",
                    quoteSymbol, changeRatio,stockInfo.PrevPrice, stockTick.Price);
            }
            _stockInfos[quoteSymbol].PrevPrice = stockTick.Price;
        }
        else
        {
            _stockInfos[quoteSymbol] =
            new StockInfo(quoteSymbol, stockTick.Price);
        }
    }
}

在许多情况下,使用锁是一个完美的解决方案。但是,当您开始在应用程序的各个位置添加锁时,最终可能会导致性能下降,因为锁会增加执行时间以及线程等待关键部分可用的时间。更困难的问题是,锁可能会导致应用程序陷入死锁,如图2.3所示。每个线程都持有另一个线程所需的资源,同时它们都在等待另一个所持有的资源。

在这里插入图片描述
图2.3死锁:线程1持有资源R1并等待资源R2可用。同时,线程2正在持有资源R2并等待资源R1。如果没有外部干预,两个线程将永远保持锁定状态。

处理多线程应用程序是困难的,并且不存在神奇的解决方案。唯一合理的做法是使多线程运行的代码更容易理解,并使处理并发代码的陷阱更加困难。

Rx提供了运行并发代码的操作符,你将在本章后面看到。现在,让我们后退一步,看看您已经创建了什么,并分析它,看看您是否可以做得更好。

——未完待续

——重庆教主(QQ23611316) 2024.05.19

——WPF中文网 wpfsoft.com

  • 18
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值