多线程的那群“象”

  最初学习多线程的时候,只学了用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,一经查实,立即删除!

相关文章

反馈页面设计_获得设计反馈的艺术

反馈页面设计I’m going to assume that you already know the importance of sharing work early and often to get feedback from your team and stakeholders. And how it’s such a critical part of the design process. Cool. Glad we’re on the same page.我假设您已经…

转 求结构体偏移

原文出处忘记了。。。 此宏很神奇 #define FIND(structTest,e) (size_t)&(((structTest*)0)->e) struct test { int a; int b; char c; int d; }; size_t s FIND(test,a); cout<<s<<endl; //输出结构体偏移转载于:https://www.cnblogs.com/zhangdongsheng…

系统设计原则的重要性_设计原则的重要性及其对好的设计的影响

系统设计原则的重要性The principles of design are the most important part of any design process. Without these principles, it would be very difficult for the users to understand the type of message that the designer is trying communicate.设计原则是任何设计过…

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

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

有一种爱情叫永不改变_设计就像爱情一样,总是在寻找一种方式

有一种爱情叫永不改变If you’re a designer, you know what I’m talking about. You just pitched some amazing brand extension ideas to your client and the worst thing happened. They killed the very best one, the one you have a ton of heart for. You know it ca…

linux网络编程系列-select和epoll的区别

select和epoll属于I/O多路复用模型&#xff0c;用于持续监听多个socket&#xff0c;获取其IO事件。 select&#xff08;轮询&#xff09; 该模型轮询各socket&#xff0c;不管socket是否活跃&#xff0c;随着socket数的增加&#xff0c;性能逐渐下降。 #include <sys/select…

产品原型制作_早期制作原型如何帮助您设计出色的数字产品

产品原型制作Utilizing prototypes this way, is a missed shot. A missed shot to create an outcome that solves a real problem for customers. An outcome that is worthwhile. Prototypes are a great tool to discover what people need, what they dream of, what thei…

ThinkPHP add、save无法添加、修改不起作用

ThinkPHP add、save无法添加、修改不起作用 案例&#xff1a;数据库新添加一字段&#xff0c;修改值不成功。解决方案&#xff1a;将Runtime/Data/_fields/下面的字段缓存删除&#xff0c;或者直接删除整个Runtime文件也是可以的分析&#xff1a;由于Thinkphp&#xff0c;采用字…

photoshop最新版本_iPad Pro应该拥有更好的Photoshop版本

photoshop最新版本I remember when Adobe came to an Apple Keynote in 2018 to show how powerful was Photoshop on the new iPad Pros.我记得Adobe在2018年参加Apple Keynote时展示了Photoshop在新iPad Pro上的强大功能。 In fact, like everyone else, I was blown away, …

android 辅助功能_关于辅助功能的9个神话

android 辅助功能Most designers don’t know about accessibility or have misconceptions about it, such as thinking it will hinder their creativity or that it doesn’t apply to their clients. Find out 9 myths about accessibility and why you and your clients s…

【语言处理与Python】1.5自动理解自然语言

【词义消歧】在词义消歧中&#xff0c;我们要算出特定上下文中词被赋予的是哪个意思。自动消除歧义需要使用上下文&#xff0c;利用相邻词汇有相近含义这样一个简单的事实。【指代消解】解决“谁对谁做了什么”&#xff0c;即监测主语和动词的宾语。确定带刺或名词短语指的是什…

绊倒在舌头上

It’s not something you’d confess in tenth grade, but I’ve always been fascinated by typography. By crisp, pared down symbols in clear white space, the infinite variety of lines, ligatures and curves that make up the letters of language. Even the sample …

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

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

前端开发时间格式的转换方法_开发人员投资时间而不浪费时间的10种方法

前端开发时间格式的转换方法In today’s, in the past and probably in the future world — the time is more valuable than money, and the right time waits for no one. Hence, we have to make the most out of it to succeed in life.在当今&#xff0c;过去甚至未来世界…

链表基本操作

单链表结构&#xff1a; typedef struct node { int data; struct node *next; }node&#xff1b; typedef struct node *LinkList; /*创建单链表&#xff0c;将新的节点插入到链表的尾部*/ createList(LinkList L, int n) { LinkList p,r;  //p节点用来接收插入的元素&#…

python 投资组合_重新设计投资组合的好处

python 投资组合Yep, I’m here to nag you a bit about that portfolio that you haven’t updated in a while.是的&#xff0c;我在这里想和您谈谈您有一段时间没有更新的作品集。 Yes, it’s time to get to work on it again.是的 &#xff0c;是时候重新开始研究了。 Y…

李安的电影梦by李安

1978年,当我准备报考美国伊利诺大学的戏剧电影系时&#xff0c;父亲十分反感&#xff0c;他给我举了一个数字&#xff1a;在美国百老汇&#xff0c;每年只有200个角&#xff0c;但却有50000人要一起争夺这少得可怜的角色。当时我一意孤行&#xff0c;决意登上了去美国的班机&am…

抓取html中用到的css_如何使用HTML和CSS制作像《星球大战》一样的抓取文字

抓取html中用到的cssThe opening to Star Wars is iconic. The effect of text scrolling both up and away from the screen was both a crazy cool special effect for a movie back in 1977 and a cool typographical style that was brand new at the time.《星球大战》的开…

不安装游戏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…

web字体设置成平方字体_Web字体正确完成

web字体设置成平方字体When using web fonts we must carefully avoid its hidden pitfalls. To do this designers, frontend developers, DevOps, and authors each have a role to play in creating a great end-user experience.使用网络字体时&#xff0c;必须小心避免其隐…