多线程的那群“象”

  最初学习多线程的时候,只学了用Thread这个类,记忆中也用过Mutex,到后来只记得Thread的使用,其余的都忘了。知道前不久写那个Socket连接池时遇到了一些对象如:Semaphore,Interlocked,Mutex等,才知道多线程中有这么多好东西,当时用了一下有初步了解,现在来熟悉熟悉。

  本文介绍的多线程这个“象群”包括:Interlocked,Semaphore,Mutex,Monitor,ManualResetEvent,AutoRestEvent。而使用的例子则有车票竞抢和类似生产者消费者的Begin/End(这里的Begin/End跟异步里面的没关系)两个事件模型。

先来看一下本文“象群”的类图

 

Interlocked为多个线程共享的变量提供原子操作

  在平常多线程中为了保护某个互斥的资源在多线程中不会因为资源共享而出问题,都会使用lock关键字。如果这个资源只是一个单单的计数量的话,就可以用这个Interlocked了,调用Increment方法可以是递增,Decrement则是递减。下面则是MSDN上的说明

  此类的方法可以防止可能在下列情况发生的错误:计划程序在某个线程正在更新可由其他线程访问的变量时切换上下文;或者当两个线程在不同的处理器上并发执行时。 此类的成员不引发异常。

  由于这里就使用车票竞抢的例子吧!假设有10张车票,有多个售票点去销售,卖光就没有了

这个是线程的方法

 1         public void ThreadingCount2()
 2         {
 3                 while (true)
 4                 {
 5                     //卖光就停止销售了
 6                     if (count >= 10)
 7                         break;
 8                     Interlocked.Increment(ref count);
 9                     //抢到车票的要帮上售票点和座位号
10                     Console.WriteLine(Thread.CurrentThread.ManagedThreadId + " : " + count);
11                     //为了防止机子的性能太好,资源都都给一个线程抢光了,就休眠一段时间
12                     Thread.Sleep(500);
13                 }
14         }

 

这里开三个线程,模拟三个售票点去卖这10张票

 1         private void MutexTest()
 2         {
 3             count = 0;
 4             Thread t1 = new Thread(ThreadingCount2);
 5             Thread t2 = new Thread(ThreadingCount2);
 6             Thread t3 = new Thread(ThreadingCount2);
 7             t1.Start();
 8             t2.Start();
 9             t3.Start();
10         }

 

运行结果

 

Semaphore (限制可同时访问某一资源或资源池的线程数)

  这个称之为信号量,也有些人叫它作信号灯。这个概念倒是在操作系统中听过,现在用起来就感觉可以通过信号量来限制进入某段区域的次数,通过调用WaitOne和Release方法,这个挺适合生产者与消费者那个问题的。记得解决生产者与消费者的问题上有用到这个信号量。下面则是MSDN的说明:

  使用 Semaphore 类可控制对资源池的访问。 线程通过调用 WaitOne 方法(从 WaitHandle 类继承)进入信号量,并通过调用 Release 方法释放信号量。

  信号量的计数在每次线程进入信号量时减小,在线程释放信号量时增加。 当计数为零时,后面的请求将被阻塞,直到有其他线程释放信号量。 当所有的线程都已释放信号量时,计数达到创建信号量时所指定的最大值。

  被阻止的线程并不一定按特定的顺序(如 FIFO 或 LIFO)进入信号量。

  下面则用Begin/End模型来作为例子,它这不停地交替输出Begin和End,每输出一次Begin,就会暂停,直到输出了一次End,才会输出下一个Begin。用两个线程,一个是专门输出Begin的;另一个是输出End的。

Begin的线程方法如下

1         private void Begin()
2         {
3             while (true)
4             {
5                 semaphore.WaitOne();
6                 Console.WriteLine(Thread.CurrentThread.ManagedThreadId+ " Begin");
7             }
8         }

 

  这里先是等待信号才去输出,这个输出就相当于进行某一些操作了,如果把Waitone放到输出的后面,就限制不了对某个操作进行次数限制。当然,这样做的话,对semaphore对象构造时也会不同。

End的线程方法如下

1         private void End()
2         {
3             while (true)
4             {
5                 Thread.Sleep(1000);
6                 Console.WriteLine(Thread.CurrentThread.ManagedThreadId + " End");
7                 semaphore.Release();
8             }
9         }

  这里休眠1秒作用有两个,第一是等待Begin先运行才释放信号,第二是控制输出的节奏,免得屏幕上猛的刷一大堆Begin/End,看不清什么东西了。

  Semaphore构造时是这样的semaphore = new Semaphore(1, 1);第一个参数是初始化时的信号量,第二个参数是总的信号量,调用则是这样,两个线程输出Begin,一个线程数据End

1             Thread t1 = new Thread(Begin);
2             Thread t3 = new Thread(Begin);
3             Thread t2 = new Thread(End);
4             t1.Start();
5             t3.Start();
6             t2.Start();

  运行的结果,两个线程会抢着输出Begin,输出了Begin之后就会被阻塞,等到End输出了之后才能进行下一次争夺Begin的输出

有位园友说,我老是用那个Sleep方法不好,于是这里就给一个没有用Sleep方法的Begin/End版本。

用到的信号量就要两个了,一个是用于阻塞Begin的,一个是用于阻塞End的,初始时值也有出入。End的要让它先阻塞,Begin的要让它先通过

1             private Semaphore semBegin, semEnd;
2             semBegin = new Semaphore(1, 1);
3             semEnd = new Semaphore(0, 1);    

 

 1         private void Begin()
 2         {
 3             for (int i = 0; i < 5; i++)
 4             {
 5                 semBegin.WaitOne();
 6                 Console.WriteLine(Thread.CurrentThread.ManagedThreadId+" : Begin ");
 7                 semEnd.Release();
 8             }
 9         }
10 
11         private void End()
12         {
13             while (true)
14             {
15                 semEnd.WaitOne();
16                 Console.WriteLine(Thread.CurrentThread.ManagedThreadId+" : End ");
17                 semBegin.Release();
18             }
19         }
20     }

 

这样使用信号量有死锁的嫌疑,但是实践过是没有的。运行结果与之前的一样,暂时不考虑信号量的关闭与线程关闭等问题。

 

Mutex (一个同步基元,也可用于进程间同步)

  这个称之为互斥体。这个互斥体跟lock关键字差不多,是保证某片代码区域只能给一个线程访问,通过调用WaitOne来挂起线程等待信号和ReleaseMutex释放一次互斥信号来唤醒当前线程这样的方式来实现。这个挂起只会挂起后来进入这片区域的线程,最初的线程在唤醒之前无论遇到多少个WaitOne照样过,不过在之前WaitOne了多少次,到后来就要相应释放那么多次,否则别的线程一直被挂起到某个WaitOne处,虽然把等待和释放分开了两个方法,但放在不同线程去调用的话只会抛异常,因为这两个方法要在一个同步的区域内调用的。下面则是MSDN的说明。

  当两个或更多线程需要同时访问一个共享资源时,系统需要使用同步机制来确保一次只有一个线程使用该资源。 Mutex 是同步基元,它只向一个线程授予对共享资源的独占访问权。 如果一个线程获取了互斥体,则要获取该互斥体的第二个线程将被挂起,直到第一个线程释放该互斥体。

  既然这个互斥体的用法跟lock那么相像,我用抢车票的例子吧!这里变的只是线程的方法而已,创建线程的跟原来的一样,不再重复粘贴了

 1         private void ThreadingCount()
 2         {
 3             while (true)
 4             {
 5                 mutex.WaitOne();
 6                     if (count > 10)
 7                     {
 8                         mutex.ReleaseMutex();
 9                         break;
10                     }
11                     Console.WriteLine(Thread.CurrentThread.ManagedThreadId + " : " + count++);
12                 mutex.ReleaseMutex();
13                 Thread.Sleep(500);
14             }
15         }

 

构造对象时这样mutex = new Mutex();,运行结果如下

 

ManualResetEvent(通知一个或多个正在等待的线程已发生事件)与AutoResetEvent (通知正在等待的线程已发生事件)

  这两个类很相似,都是调用了WaitOne就阻塞当前线程等待信号,直到调用了Set才发了信号唤醒阻塞的线程。不同点就在调用Set方法之后了,AutoResetEvent 只是唤醒一个线程,但是就唤醒了所有等待信号而阻塞的线程,并且需要调用Reset关闭了信号,才能使WaitOne处能阻塞线程。下面分别是MSDN上对它们的描述

       ManualResetEvent 使线程可以通过发信号来互相通信。 通常,此通信涉及一个线程在其他线程进行之前必须完成的任务。

       AutoResetEvent 使线程可以通过发信号来互相通信。 通常,此通信涉及线程需要独占访问的资源。

       这里就用Begin/End的作例子

两个类用起来基本一样,就效果一样而已,出于篇幅的考虑,只上一次代码算了

 1         private void Begin()
 2         {
 3             while (true)
 4             {
 5                 //等待信号
 6                 //autoreset.WaitOne();
 7                 manualreset.WaitOne();
 8                 Console.WriteLine(Thread.CurrentThread.ManagedThreadId+ " Begin");
 9                 //关闭信号
10                 manualreset.Reset();
11                 //这里对于autorest来说其实可以需要
12                 //因为调用Set()之后就会关闭信号了
13                 //autoreset.Reset();
14             }
15         }
16 
17         private void End()
18         {
19             while (true)
20             {
21                 Thread.Sleep(1000);
22                 Console.WriteLine(Thread.CurrentThread.ManagedThreadId + " End");
23                 //semaphore.Release();
24                 manualreset.Set();
25                 //autoreset.Set();
26             }
27         }

 

  阻塞线程和输出Begin的道理和前面使用Semaphore 的一样,都是为了确保能互斥地执行那个操作,可是对于使用ManualResetEvent 就不是这样说了,看看结果就知道了

  这个是ManualResetEvent 的运行结果,一发出了信号,之前等待信号的两个线程都同时被唤醒了,一齐去输出Begin,两个线程又在关闭信号之后阻塞在等待信号的地方。

  而AutoResetEvent 的结果则不同,Begin和End都是一个挨着一个交替输出,那个线程抢到了信号就能输出Begin,抢不到的就一直阻塞在那里。

对了,两个对象的构造如下

            manualreset = new ManualResetEvent(true);autoreset = new AutoResetEvent(true);

true是初始状态,true就一开始有信号,免得没信号就一直卡在那里,要等End执行了才放行,这样有了End才有Begin就不对了。

这里也同样给出不用Sleep的版本,同样所需要的对象也比原本的多了

1         private ManualResetEvent manBegin, manEnd;
2 
3         private AutoResetEvent autoBegin, autoEnd;
4 
5             manBegin = new ManualResetEvent(true);
6             manEnd = new ManualResetEvent(false);
7 
8             autoBegin = new AutoResetEvent(true);
9             autoEnd = new AutoResetEvent(false);

 

初始状态跟上面使用信号量的道理一样。

 1         private void Begin()
 2         {
 3             for (int i = 0; i < 5; i++)
 4             {
 5                 manBegin.WaitOne();
 6                 //manBegin.Reset();//在这里Reset就只能是一个Begin一个End
 7                 //autoBegin.WaitOne();
 8                 Console.WriteLine(Thread.CurrentThread.ManagedThreadId+" : Begin ");
 9                 manBegin.Reset();//在这里Reset就两个Begin一个End
10                 manEnd.Set();
11                 //autoEnd.Set();
12             }
13         }
14 
15         private void End()
16         {
17             while (true)
18             {
19                 manEnd.WaitOne();
20                 manEnd.Reset();
21                 //autoEnd.WaitOne();
22                 Console.WriteLine(Thread.CurrentThread.ManagedThreadId+" : End ");
23                 manBegin.Set();
24                 //autoBegin.Set();
25             }
26         }
27     }

 

这里使用ManualResetEvent类的时候有两种情况,注释中有说明,关闭信号的地方不同,会影响到Begin输出的数量,在这里也用ManualResetEvent类实现Begin和End间隔输出。

 

Monitor提供同步访问对象的机制

  这个类是在网上看别人的博文时看到的,这个类比较原始。还是先看看MSDN的说明吧!

  Monitor类通过向单个线程授予对象锁来控制对对象的访问。 对象锁提供限制访问代码块(通常称为临界区)的能力。 当一个线程拥有对象的锁时,其他任何线程都不能获取该锁。 还可以使用 Monitor 来确保不会允许其他任何线程访问正在由锁的所有者执行的应用程序代码节,除非另一个线程正在使用其他的锁定对象执行该代码。

  Enter方法和Exit方法已经被封装成lock关键字了。这里也给个使用EnterExit方法的例子,抢票问题的

 1         private void ThreadingCount()
 2         {
 3             while (true)
 4             {
 5                 Monitor.Enter(objFlag);
 6                     if (count > 10)
 7                     {
 8                         Monitor.Exit(objFlag);
 9                         break;
10                     }
11                     Console.WriteLine(Thread.CurrentThread.ManagedThreadId + " : " + count++);
12                 Thread.Sleep(500);
13                     Monitor.Exit(objFlag);
14             }
15         }

 

  Enter和Exit方法都要传一个object类型的参数,作用就跟lock的锁旗标一样。

  Monitor除了能实现抢票这类的问题外,同样也能解决Begin/End的问题的。它有个Wait和Pluse方法。下面则列举出另一个例子的代码

 1         private void Begin()
 2         {
 3             lock (objFlag)
 4             {
 5                 Monitor.Pulse(objFlag);
 6             }
 7             while (true)
 8             {
 9                 lock (objFlag)
10                 {
11                     //调用Wait方法释放对象上的锁并阻止该线程(线程状态为WaitSleepJoin)
12                     //该线程进入到同步对象的等待队列,直到其它线程调用Pulse使该线程进入到就绪队列中
13                     //线程进入到就绪队列中才有条件争夺同步对象的所有权
14                     //如果没有其它线程调用Pulse/PulseAll方法,该线程不可能被执行
15                     Monitor.Wait(objFlag);
16                     Console.WriteLine(Thread.CurrentThread.ManagedThreadId + " Begin");
17                 }
18             }
19         }
20 
21         private void End()
22         {
23             Thread.Sleep(1000);
24             while (true)
25             {
26                 lock (objFlag)
27                 {
28                     //通知等待队列中的线程锁定对象状态的更改,但不会释放锁
29                     //接收到Pulse脉冲后,线程从同步对象的等待队列移动到就绪队列中
30                     //注意:最终能获得锁的线程并不一定是得到Pulse脉冲的线程
31                     Monitor.Pulse(objFlag);
32 
33                     Console.WriteLine(Thread.CurrentThread.ManagedThreadId + " End");
34                     //释放对象上的锁并阻止当前线程,直到它重新获取该锁
35                     //如果指定的超时间隔已过,则线程进入就绪队列
36                     Monitor.Wait(objFlag, 1000);
37                 }
38             }
39         }
40     }

  当然这个例子其实挺生搬硬套的,为了让Begin先输出,就Pluse一次,同时又让End的线程休眠。如果Begin的线程不运行,End的照样能正常输出,这里希望各位有什么高见的不要吝啬,尽管提出来。下面是运行结果。

  上面如果有什么不足的或遗漏的或说错的,请各位尽情指出。谢谢!

转载于:https://www.cnblogs.com/HopeGi/archive/2013/05/08/3066129.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/274261.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

优秀HTML5网站学习范例:从“饥饿游戏浏览器”谈用户体验

继影片《饥饿游戏》获得票房成功后&#xff0c;《饥饿游戏2&#xff1a;火星燎原》也于2012年宣布开拍&#xff0c;将在今年的11月22日登陆全球各大院线。值此之际&#xff0c;微软携手美国狮门影业公司和 RED Interactive Agency 一起为影迷打造了一个基于 HTML5 现代网页规范…

[开源]jquery.ellipsis根据宽度(不是字数)进行内容截断,支持多行内容

jquery.ellipsis 自动计算内容宽度&#xff08;不是字数&#xff09;截断&#xff0c;并加上省略号&#xff0c;内容不受中英文或符号限制。 如果根据字数来计算的话&#xff0c;因为不同字符的宽度并不相同&#xff0c;比如l和W&#xff0c;特别是中英文&#xff0c;最终内容宽…

不安装游戏apk直接启动法

原文地址&#xff1a;http://blog.zhourunsheng.com/2011/09/%E6%8E%A2%E7%A7%98%E8%85%BE%E8%AE%AFandroid%E6%89%8B%E6%9C%BA%E6%B8%B8%E6%88%8F%E5%B9%B3%E5%8F%B0%E4%B9%8B%E4%B8%8D%E5%AE%89%E8%A3%85%E6%B8%B8%E6%88%8Fapk%E7%9B%B4%E6%8E%A5%E5%90%AF%E5%8A%A8%E6%B3%95…

Android客户端打包方案分享

基本介绍 Android应用的自动化打包是应用持续集成以及多渠道发布的基础。当前Android客户端自动化打包的主要有两种方式&#xff0c;Ant和Maven。两种方式本质上都是调用Android SDK里面提供的工具&#xff0c;不过各自有各自的特点。 1. Ant脚本 好处&#xff1a;开发成本较低…

您的UX库不只是书籍

hp ux 密码不过期Looking back on past self, one thing I wish I’d realised is the importance of keeping notes of everything.回顾过去的自我&#xff0c;我希望我意识到的一件事是记录所有事情的重要性。 This means everything interesting I’ve read and written; e…

交互设计精髓_设计空间的精髓

交互设计精髓重点 (Top highlight)什么是空间&#xff1f; (What is Space?) Space is the dimension of height, depth and width within which all things exist and move. Space or Empty space or White space or Negative space are alias given to describe intensional…

ux和ui_UI和UX设计人员的47个关键课程

ux和ui重点 (Top highlight)This is a mega-list of the most critical knowledge for UI, UX, interaction, or product designers at any level.这是所有级别的UI&#xff0c;UX&#xff0c;交互或产品设计人员最关键的知识的大清单。 Many of these lessons are also appli…

深入理解Java内存模型(七)——总结

处理器内存模型 顺序一致性内存模型是一个理论参考模型&#xff0c;JMM和处理器内存模型在设计时通常会把顺序一致性内存模型作为参照。JMM和处理器内存模型在设计时会对顺序一致性模型做一些放松&#xff0c;因为如果完全按照顺序一致性模型来实现处理器和JMM&#xff0c;那么…

沉浸式ui设计_有助于沉浸的视频游戏UI —武器轮

沉浸式ui设计Many action-adventure games rely on the feeling of thrills via bullets, fire, grenade, more bullets, and gigantic booms. The way to enable all these is to offer a massive arsenal, from machetes to assault rifles all the way till bazookas.许多动…

ux设计师薪水_客户现在也是UX设计师

ux设计师薪水Some of you probably know by now, I’m not too fond of the monster the UX industry has become. It’s overblown, overcomplicated and often dishonest towards the clients. It’s also in itself undefined. (where is the E in Experience?)你们中的某些…

分步表单_角色创建分步指南

分步表单The first thing most of us designers are taught is the concept of personas and the necessity of them when it comes to UX and product design. However, knowing is different from applying and it can be difficult to know where to begin when we’re aske…

svg配合css3动画_带有Adobe Illustrator,HTML和CSS的任何网站的SVG动画

svg配合css3动画A top trend in web design for 2020 is the increased use of SVG animations on web pages and in logo design. In this article, we will implement a simple and straight forward method to create relatively complex animation. We will use Adobe Illu…

基于pt100温度计仿真_基于8pt网格的设计系统

基于pt100温度计仿真重点 (Top highlight)This article is the 2nd in a two part series — to the previous chapter in which I demonstrate how to establish an 8pt grid.本文是该系列文章的第二部分 &#xff0c;这是上一章 的第二部分 &#xff0c;在上一章中&#xff0…

利用 k8s 建立软件商店_为企业建立应用商店

利用 k8s 建立软件商店It’s June 2019. I’m sitting in a conference room in Research Triangle Park in North Carolina. At the end of the table are the two executives that have been tapped to lead a new endeavor on behalf of IBM’s $34 billion acquisition of …

苹果复兴_类型复兴的故事:来自Type West的经验教训

苹果复兴Last Fall, I began the 去年秋天&#xff0c;我开始 在旧金山的Type West program at the Letterform档案库中Letterform Archive in San Francisco. For those of you who don’t know, the Letterform Archive is creative heaven — a type nerd’s letter art co…

C#调用ATL COM

作者&#xff1a;朱金灿 来源&#xff1a;http://blog.csdn.net/clever101 简单介绍C#程序如何调用ATL编写的COM组件。 首先新建一个ATL工程&#xff0c;具体如下&#xff1a; 1. 填写工程名称和路径&#xff0c;如下图&#xff1a; 2. 选择工程的服务器类型为动态链接库&a…

浪潮世科和浪潮软件什么关系_社交图形浪潮

浪潮世科和浪潮软件什么关系Nowadays, the cornucopia of graphics seems like a given. However, it was not so long ago that infographics were scarce and lived in closed ecosystems. The majority of graphics were published in newspapers, magazines, or books, and…

PHP图形图像的典型应用 --常用图像的应用(验证码)

php生成动态的验证码&#xff0c;是php防止恶意登陆或者注册等常规手段-废话不多说&#xff0c;直接看例子。&#xff08;只是一个简单的应用&#xff0c;如果要安全或者更复杂的&#xff0c;请期待我以后的文章&#xff09; PHP生成验证码核心文件 (checks.php): <?php/*成…

写saas创业的书_我在SaaS创业公司担任UX设计师的第一个月中学到的三件事

写saas创业的书I recently transitioned from being a copywriter at an ad agency to a UX Designer at a SaaS startup. To add more multidisciplinary skills into the mix, I graduated with a Bachelor in Accountancy.我最近从一名广告代理商的撰稿人过渡到了SaaS初创公…

ui项目答辩中学到了什么_我在UI设计9年中学到的12件事

ui项目答辩中学到了什么重点 (Top highlight)I know these can seem a bit clich but I will try to explain everything from my own experience.我知道这些内容似乎有些陈词滥调&#xff0c;但我会尝试根据自己的经验来解释所有内容。 第一名 (No.1 Never assume) The first…