多线程编程(2):线程的同步
在《多线程编程》系列第一篇讲述了如何启动线程,这篇讲述线程之间存在竞争时如何确保同步并且不发生死锁。 线程不同步引出的问题 下面做一个假设,假设有100张票,由两个线程来实现一个售票程序,每次线程运行时首先检查是否还有票未售出,如果有就按照票号
在《多线程编程》系列第一篇讲述了如何启动线程,这篇讲述线程之间存在竞争时如何确保同步并且不发生死锁。
线程不同步引出的问题
下面做一个假设,假设有100张票,由两个线程来实现一个售票程序,每次线程运行时首先检查是否还有票未售出,如果有就按照票号从小到大的顺序售出票号最小的票,程序的代码如下:
这段程序的执行效果并不每次都一样,下图是某次运行效果的截图:
从上图可以看出票号为001的号被售出了两次(如果遇上像《无极》中谢霆锋饰演的那种角色,可能又会引出一场《一张票引发的血案》了,呵呵),为什么会出现这种情况呢?
请看代码③处:
ticketList.RemoveAt(0);//③
在某个情况有可能线程1恰好运行到此处,从ticketList中取出索引为0的那个元素并将票号输出,不巧的是正好分给线程1执行的时间片已用完,线程1进入休眠状态,线程2从头开始执行,它可以从容地从ticketList中取出索引为0的那个元素并且将其输出,因为线程1执行的时候虽然输出了ticketList中索引为0的那个元素但是来不及将其删除,所以这时候线程2得到的值和上次线程1得到的值一致,这就出现了有些票被售出了两次、有些票可能根本就没有售出的情况。
出现这种情况的根本原因就是多个线程都是对同一资源进行操作所致,所以在多线程编程应尽可能避免这种情况,当然有些情况下确实避免不了这种情况,这就需要对其采用一些手段来确保不会出现这种情况,这就是所谓的线程的同步。
在C#中实现线程的同步有几种方法:lock、Mutex、Monitor、Semaphore、Interlocked和ReaderWriterLock等。同步策略也可以分为同步上下文、同步代码区、手动同步几种方式。
同步上下文
同步上下文的策略主要是依靠SynchronizationAttribute类来实现。例如下面的代码就是一个实现了上下文同步的类的代码:
所有在同一个上下文域的对象共享同一个锁。这样创建的对象实例属性、方法和字段就具有线程安全性,需要注意的是类的静态字段、属性和方法是不具有线程安全性的。
同步代码区
同步代码区是另外一种策略,它是针对特定部分代码进行同步的一种方法。
lock同步
针对上面的代码,要实现不会出现混乱(两次卖出同一张票或者有些票根本就没有卖出),可以lock关键字来实现,出现问题的部分就是在于判断剩余票数是否大于0,如果大于0则从当前总票数中减去最大的一张票,因此可以对这部分进行处理,代码如下:
经过这样处理之后系统的运行结果就会正常。效果如下:
总的来说,lock语句是一种有效的、不跨越多个方法的小代码块同步的做法,也就是使用lock语句只能在某个方法的部分代码之间,不能跨越方法。
Monitor类
针对上面的代码,如果使用Monitor类来同步的话,代码则是如下效果:
当然这段代码最终运行的效果也和使用lock关键字来同步的效果一样。比较之下,大家会发现使用lock关键字来保持同步的差别不大:”lock (objLock){“被换成了”Monitor.Enter(objLock);”,”}”被换成了” Monitor.Exit(objLock);”。实际上如果你通过其它方式查看最终生成的IL代码,你会发现使用lock关键字的代码实际上是用Monitor来实现的。
如下代码:
实际上是相当于:
我们知道在绝大多数情况下finally中的代码块一定会被执行,这样确保了即使同步代码出现了异常也仍能释放同步锁。
Monitor类出了Enter()和Exit()方法之外,还有Wait()和Pulse()方法。Wait()方法是临时释放当前活得的锁,并使当前对象处于阻塞状态,Pulse()方法是通知处于等待状态的对象可以准备就绪了,它一会就会释放锁。下面我们利用这两个方法来完成一个协同的线程,一个线程负责随机产生数据,一个线程负责将生成的数据显示出来。下面是代码:
执行上面的代码在大部分情况下会看到如下所示的结果:
一般情况下会看到上面的结果,原因是t1的Start()方法在先,所以一般会优先活得执行,t1执行后首先获得对象锁,然后在循环中通过 Monitor.Wait(lockObject)方法临时释放对象锁,t1这时处于阻塞状态;这样t2获得对象锁并且得以执行,t2进入循环后通过Monitor.Pulse(lockObject)方法通知等待同一个对象锁的t1准备好,然后在生成随机数之后临时释放对象锁;接着t1获得了对象锁,执行输出t2生成的数据,之后t1通过 Monitor.Wait(lockObject)通知t2准备就绪,并在下一个循环中通过 Monitor.Wait(lockObject)方法临时释放对象锁,就这样t1和t2交替执行,得到了上面的结果。
当然在某些情况下,可能还会看到如下的结果:
至于为什么会产生这个结果,原因其实很简单,尽管t1.Start()出现在t2.Start()之前,但是并不能就认为t1一定会比t2优先执行(尽管可能在大多数情况下是),还要考虑线程调度问题,使用了多线程之后就会使代码的执行顺序变得复杂起来。在某种情况下t1和t2对锁的使用产生了冲突,形成了死锁,也就出现了如上图所示的情况,为了避免这种情况可以通过让t2延时一个合适的时间。
手控同步
手控同步是指使用不同的同步类来创建自己的同步机制。使用这种策略要求手动地为不同的域或者方法同步。
ReaderWriterLock
ReaderWriterLock支持单个写线程和多个读线程的锁。在任一特定时刻允许多个线程同时进行读操作或者一个线程进行写操作,使用ReaderWriterLock来进行读写同步比使用监视的方式(如Monitor)效率要高。
下面是一个例子,在例子中使用了两个读线程和一个写线程,代码如下:
程序的运行结果如下:
Result = 0.355650523270459.
Result = 0.125205692112756.
当然因为引入了随机数,所以每次计算结果并不相同,这里要讲述的是它们之间的控制。首先在 Result(int seed)方法中讲计算基数、第一项、第二项及第三项的方法放到线程池中,要计算第一二三项时首先要确定基数,这些方法通过manualEvent.WaitOne()暂时停止执行,于是计算基数的方法首先执行,计算出基数之后通过manualEvent.Set()方法通知计算第一二三项的方法开始,在这些方法完成计算之后通过autoEvents数组中的AutoResetEvent元素的Set()方法发出信号,标识执行完毕。这样WaitHandle.WaitAll(autoEvents)这一步可以继续执行,从而得到执行结果。
在上面代码中的WaitHandle的其它子类限于篇幅不在这里一一举例讲解,它们在使用了多少有些相似之处(毕竟是一个爹、从一个抽象类继承下来的嘛)。
图片上传功能暂时关闭,敬请谅解。

热AI工具

Undresser.AI Undress
人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover
用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool
免费脱衣服图片

Clothoff.io
AI脱衣机

AI Hentai Generator
免费生成ai无尽的。

热门文章

热工具

记事本++7.3.1
好用且免费的代码编辑器

SublimeText3汉化版
中文版,非常好用

禅工作室 13.0.1
功能强大的PHP集成开发环境

Dreamweaver CS6
视觉化网页开发工具

SublimeText3 Mac版
神级代码编辑软件(SublimeText3)

热门话题

使用正则表达式从PHP数组中去除重复值的方法:使用正则表达式/(.*)(.+)/i匹配并替换重复项。遍历数组元素,使用preg_match检查匹配情况。如果匹配,跳过值;否则,将其添加到无重复值的新数组中。

小米15系列预计将于10月份正式发布,其全系列代号已在外媒MiCode代码库中曝光。其中,旗舰级小米15Ultra代号为"Xuanyuan"(意为"轩辕"),此名源自中国神话中的黄帝,象征着尊贵。小米15的代号为"Dada",而小米15Pro则以"Haotian"(意为"昊天")为名。小米15SPro内部代号为"dijun",暗指《山海经》创世神帝俊。小米15Ultra系列涵盖

多线程中,读写锁允许多个线程同时读取数据,但只允许一个线程写入数据,以提高并发性和数据一致性。C++中的std::shared_mutex类提供了以下成员函数:lock():获取写入访问权限,当没有其他线程持有读取或写入锁时成功。lock_read():获取读取访问权限,可与其他读取锁或写入锁同时持有。unlock():释放写入访问权限。unlock_shared():释放读取访问权限。

自去年华为Mate60系列开售以来,我个人就一直将Mate60Pro作为主力机使用。在将近一年的时间里,华为Mate60Pro经过多次OTA升级,综合体验有了显着提升,给人一种常用常新的感觉。比如近期,华为Mate60系列就再度迎来了影像功能的重磅升级。首先是新增AI消除功能,可以智能消除路人、杂物并对空白部分进行自动补充;其次是主摄色准、长焦清晰度均有明显升级。考虑到现在是开学季,华为Mate60系列还推出了秋日礼遇活动:购机可享至高800元优惠,入手价低至4999元。常用常新的产品力加上超值

为避免线程饥饿,可以使用公平锁确保资源公平分配,或设置线程优先级。为解决优先级反转,可使用优先级继承,即暂时提高持有资源线程的优先级;或使用锁的提升,即提升需要资源线程的优先级。

C++中线程终止和取消机制包括:线程终止:std::thread::join()阻塞当前线程直到目标线程完成执行;std::thread::detach()从线程管理中分离目标线程。线程取消:std::thread::request_termination()请求目标线程终止执行;std::thread::get_id()获取目标线程ID,可与std::terminate()一起使用,立即终止目标线程。实战中,request_termination()允许线程决定终止时机,join()确保在主线

基于Actor模型的C++多线程编程实现:创建表示独立实体的Actor类。设置存储消息的消息队列。定义Actor从队列接收并处理消息的方法。创建Actor对象,启动线程来运行它们。通过消息队列发送消息到Actor。这种方法提供了高并发性、可扩展性和隔离性,非常适合需要处理大量并行任务的应用程序。

1、编程可以用于开发各种软件和应用程序,包括网站、手机应用、游戏和数据分析工具等。它的应用领域非常广泛,覆盖了几乎所有行业,包括科学研究、医疗保健、金融、教育、娱乐等。2、学习编程可以帮助我们提高问题解决能力和逻辑思维能力。编程过程中,我们需要分析和理解问题,找出解决方案,并将其转化为代码。这种思维方式能够培养我们的分析和抽象能力,提高我们解决实际问题的能力。
