其实说到上一篇,我们要说的task的知识也说的差不多了,这一篇我们开始站在理论上了解下“线程池”和“任务”之间的关系,不管是
说线程还是任务,我们都不可避免的要讨论下线程池,然而在.net 4.0以后,线程池引擎考虑了未来的扩展性,已经充分利用多核微处理器
架构,只要在可能的情况下,我们应该尽量使用task,而不是线程池。
首先看一下task的结构

从图中我们可以看出Task.Factory.StartNew()貌似等同于用ThreadPool.QueueUserWorkItem()创建,但是请注意,我是用TPL的形式
使用线程池,要知道task出现以后,一直标榜着以更少的工作量,更低的性能消耗来PK原始线程。
这里简要的分析下CLR线程池,其实线程池中有一个叫做“全局队列”的概念,每一次我们使用QueueUserWorkItem的使用都会产生一个
“工作项”,然后“工作项”进入“全局队列”进行排队,最后线程池中的的工作线程以FIFO的形式取出,效果图类似如下:

这里要值得一提的是,在.net 4.0之后“全局队列”采用了无锁算法,相比以前版本锁定“全局队列”带来的性能瓶颈有了很大的改观。那么任务
委托的线程池不光有“全局队列”,而且每一个工作线程都有”局部队列“,效果图如下

我们的第一反应肯定就是“局部队列“有什么好处,可以考虑这样的情况,当我们new一个task的时候“工作项”就会进去”全局队列”,如果我们的
task执行的非常快,那么“全局队列“就会FIFO的非常频繁,那么有什么办法缓解呢?当我们的task在嵌套的场景下,“局部队列”就要产生效果了,
比如我们一个task里面有3个task,那么这3个task就会存在于“局部队列”中。
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 var task = Task.Factory.StartNew(() => 6 { 7 var task1 = Task.Factory.StartNew(Run1); 8 var task2 = Task.Factory.StartNew(Run2); 9 var task3 = Task.Factory.StartNew(Run3); 10 11 Task.WaitAll(new Task[] { task1, task2, task3 }); 12 }); 13 14 Console.Read(); 15 } 16 17 public static void Run1() { Thread.Sleep(100000); } 18 19 public static void Run2() { Thread.Sleep(100000); } 20 21 public static void Run3() { Thread.Sleep(100000); } 22 }

从图中可以看到,其实“局部队列“起到了一个分流的作用,也叫做”任务内联化“,”局部队列“采用的是”LIFO"的形式,其实这样的形式也是
为了提升性能之用,因为Run3送到“局部队列”中时可能还存在CPU的高速缓存中,所以从“局部队列”中取出来相对来说更快一点,最后的效
果就是Run3要理论上优先于Run2,Run1先执行。
现在我们再来考虑这样一种情况,比如有两个人,一个人干完了分配给自己的所有活,而另一个人却还有很多的活,从人情上说,闲的人应
该接手点忙的人的活,同样,对应图中“线程2“跑完了“局部队列”中的所有任务,并且同时发现”全局队列“中已经没有可以跑的”任务“了,然而
“线程1”里面还有Run1,Run2,Run3,那么此时“线程2”采用“FIFO”的形式窃取“线程1”里面的任务。

从上面种种情况我们看到,这些分流和负载都是普通ThreadPool.QueueUserWorkItem所不能办到的,所以说在.net 4.0之后,我们
尽可能的使用TPL,抛弃ThreadPool。
在.net里面异步编程模型由来已久,相信大家也知道Begin/End异步模式和事件异步模式,在task出现以后,这些东西都可以被task包装
起来,可能有人会问,这样做有什么好处,下面一一道来。
一: Begin/End模式
1: 委托
在执行委托方法的时候,我们常常会看到一个Invoke,同时也有一对你或许不常使用的BeginInvoke,EndInvoke方法对,当然Invoke方法
是阻塞主线程,而BeginInvoke则是另开一个线程。
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 var func = new Func<string, string>(i => { return i + "i can fly"; }); 6 7 var state = func.BeginInvoke("yes,", Callback, func); 8 9 Console.Read(); 10 } 11 12 static void Callback(IAsyncResult async) 13 { 14 var result = async.AsyncState as Func<string, string>; 15 16 Console.WriteLine(result.EndInvoke(async)); 17 } 18 }

下面我们用task包装一下
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 var func = new Func<string, string>(i => 6 { 7 return i + "i can fly"; 8 }); 9 10 Task<string>.Factory.FromAsync(func.BeginInvoke, func.EndInvoke, "yes,", null).ContinueWith 11 (i => 12 { 13 Console.WriteLine(i.Result); 14 }); 15 16 Console.Read(); 17 } 18 }
可以看出,task只要一句就搞定,体现了task的第一个优点:简洁。
2:流
我们发现在Stream抽象类中提供了这样两对BeginRead/EndRead,BeginWrite/EndWrite(异步读写)的方法,这样它的n多继承类都可以
实现异步读写,下面举个继承类FileStream的例子。
1 static void Main(string[] args) 2 { 3 var path = "C://1.txt"; 4 5 FileStream fs = new FileStream(path, FileMode.Open); 6 7 FileInfo info = new FileInfo(path); 8 9 byte[] b = new byte[info.Length]; 10 11 var asycState = fs.BeginRead(b, 0, b.Length, (result) => 12 { 13 var file = result.AsyncState as FileStream; 14 15 Console.WriteLine("文件内容:{0}", Encoding.Default.GetString(b)); 16 17 file.Close(); 18 19 }, fs); 20 21 Console.WriteLine("我是主线程,我不会被阻塞!"); 22 23 Console.Read(); 24 }

我们用task包装一下
1 static void Main(string[] args) 2 { 3 var path = "C://1.txt"; 4 5 FileStream fs = new FileStream(path, FileMode.Open); 6 7 FileInfo info = new FileInfo(path); 8 9 byte[] b = new byte[info.Length]; 10 11 Task<int>.Factory.FromAsync(fs.BeginRead, fs.EndRead, b, 0, b.Length, null, TaskCreationOptions.None) 12 .ContinueWith 13 (i => 14 { 15 Console.WriteLine("文件内容:{0}", Encoding.Default.GetString(b)); 16 }); 17 18 Console.WriteLine("我是主线程,我不会被阻塞!"); 19 20 Console.Read(); 21 }

其实看到这里,我们并没有发现task还有其他的什么优点,但是深入的想一下其实并不是这么回事,task能够游刃于线程并发和同步,而原始的异步
编程要实现线程同步还是比较麻烦的。
假如现在有这样的一个需求,我们需要从3个txt文件中读取字符,然后进行倒序,前提是不能阻塞主线程。如果不用task的话我可能会用工作线程
去监视一个bool变量来判断文件是否全部读取完毕,然后再进行倒序,我也说了,相对task来说还是比较麻烦的,这里我就用task来实现。
1 class Program 2 { 3 static byte[] b; 4 5 static void Main() 6 { 7 string[] array = { "C://1.txt", "C://2.txt", "C://3.txt" }; 8 9 List<Task<string>> taskList = new List<Task<string>>(3); 10 11 foreach (var item in array) 12 { 13 taskList.Add(ReadAsyc(item)); 14 } 15 16 Task.Factory.ContinueWhenAll(taskList.ToArray(), i => 17 { 18 string result = string.Empty; 19 20 //获取各个task返回的结果 21 foreach (var item in i) 22 { 23 result += item.Result; 24 } 25 26 //倒序 27 String content = new String(result.OrderByDescending(j => j).ToArray()); 28 29 Console.WriteLine("倒序结果:"+content); 30 }); 31 32 Console.WriteLine("我是主线程,我不会被阻塞"); 33 34 Console.ReadKey(); 35 } 36 37 //异步读取 38 static Task<string> ReadAsyc(string path) 39 { 40 FileInfo info = new FileInfo(path); 41 42 byte[] b = new byte[info.Length]; 43 44 FileStream fs = new FileStream(path, FileMode.Open); 45 46 Task<int> task = Task<int>.Factory.FromAsync(fs.BeginRead, fs.EndRead, b, 0, b.Length, null, TaskCreationOptions.None); 47 48 //返回当前task的执行结果 49 return task.ContinueWith(i => 50 { 51 return i.Result > 0 ? Encoding.Default.GetString(b) : string.Empty; 52 }, TaskContinuationOptions.ExecuteSynchronously); 53 } 54 }

可以看出,task的第二个优点就是:灵活性。
这里可能就有人要问了,能不能用开多个线程用read以同步的形式读取,变相的实现文件异步读取,或许我们可能常听说程序优化后,最后出现的
瓶颈在IO上面,是的,IO是比较耗费资源的,要命的是如果我们开的是工作线程走IO读取文件,那么该线程就会一直处于等待状态,不会再接收任
何的外来请求,直到线程读取到文件为止,那么我们能不能用更少的线程来应对更多的IO操作呢?答案肯定是可以的,这里就设计到了”异步IO“的
概念,具体内容可以参照百科:http://baike.baidu.com/view/1865389.htm ,有幸的是beginXXX,endXXX完美的封装了“异步IO”。
二:事件模式
这个模式常以XXXCompleted的形式结尾,我们在文件下载这一块会经常遇到,这里我也举个例子。
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 WebClient client = new WebClient(); 6 7 client.DownloadFileCompleted += new System.ComponentModel.AsyncCompletedEventHandler(client_DownloadFileCompleted); 8 9 client.DownloadFileAsync(new Uri("http://imgsrc.baidu.com/baike/abpic/item/6a600c338744ebf844a0bc74d9f9d72a6159a7ac.jpg"), 10 "1.jpg", "图片下完了,你懂的!"); 11 12 Console.WriteLine("我是主线程,我不会被阻塞!"); 13 Console.Read(); 14 } 15 16 static void client_DownloadFileCompleted(object sender, System.ComponentModel.AsyncCompletedEventArgs e) 17 { 18 Console.WriteLine("\n" + e.UserState); 19 } 20 }

先前也说了,task是非常灵活的,那么针对这种异步模型,我们该如何封装成task来使用,幸好framework中提供了TaskCompletionSource来帮助
我们快速实现。
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.IO; 6 using System.Threading.Tasks; 7 using System.Net; 8 using System.ComponentModel; 9 10 namespace ConsoleApplication4 11 { 12 class Program 13 { 14 static void Main() 15 { 16 var downloadTask = DownLoadFileInTask( 17 new Uri(@"http://www.7720mm.cn/uploadfile/2010/1120/20101120073035736.jpg") 18 , "C://1.jpg"); 19 20 downloadTask.ContinueWith(i => 21 { 22 Console.WriteLine("图片:" + i.Result + "下载完毕!"); 23 }); 24 25 Console.WriteLine("我是主线程,我不会被阻塞!"); 26 27 Console.Read(); 28 } 29 30 static Task<string> DownLoadFileInTask(Uri address, string saveFile) 31 { 32 var wc = new WebClient(); 33 34 var tcs = new TaskCompletionSource<string>(address); 35 36 //处理异步操作的一个委托 37 AsyncCompletedEventHandler handler = null; 38 39 handler = (sender, e) => 40 { 41 if (e.Error != null) 42 { 43 tcs.TrySetException(e.Error); 44 } 45 else 46 { 47 if (e.Cancelled) 48 { 49 tcs.TrySetCanceled(); 50 } 51 else 52 { 53 tcs.TrySetResult(saveFile); 54 } 55 } 56 57 wc.DownloadFileCompleted -= handler; 58 }; 59 60 //我们将下载事件与我们自定义的handler进行了关联 61 wc.DownloadFileCompleted += handler; 62 63 try 64 { 65 wc.DownloadFileAsync(address, saveFile); 66 } 67 catch (Exception ex) 68 { 69 wc.DownloadFileCompleted -= handler; 70 71 tcs.TrySetException(ex); 72 } 73 74 return tcs.Task; 75 } 76 } 77 }

第三篇来的好晚啊,上一篇说了如何向服务器推送信息,这一篇我们看看如何"快好准"的从服务器下拉信息。
网络上有很多大资源文件,比如供人下载的zip包,电影(你懂的),那么我们如何快速的进行下载,大家第一反应肯定就是多线程下载,
那么这些东西是如何做的呢?首先我们可以从“QQ的中转站里面拉一个rar下来“。

然后用fiddler监视一下,我们会发现一个有趣的现象:
第一:7.62*1024*1024≈7990914 千真万确是此文件
第二:我明明是一个http链接,tmd的怎么变成n多个了?有意思。

好,我们继续往下看,看看这些链接都做了些什么?


最终,我们发现http协议中有一个Conent—Range字段,能够把我们的文件总大小进行切分,然后并行下载,最后再进行合并,大概我们知道
了什么原理,那么,我们强大的C#类库提供了AddRange来获取Http中资源的指定范围。
既然进行了切分,那么首先一定要知道文件的ContentLength是多少,如果对http协议比较熟悉的话,当发送一个头信息过去,服务器返回的
头信息中会包含很多东西,此时我们就知道要下载资源的大概情况,这个就有点“兵马未动,粮草先行“的感觉。

1 var request = (HttpWebRequest)HttpWebRequest.Create(url); 2 3 request.Method = "Head"; 4 5 request.Timeout = 3000; 6 7 var response = (HttpWebResponse)request.GetResponse(); 8 9 var code = response.StatusCode; 10 11 if (code != HttpStatusCode.OK) 12 { 13 Console.WriteLine("下载资源无效!"); 14 return; 15 } 16 17 var total = response.ContentLength;
这里有个决策,到底是以下载量来决定线程数,还是以线程数来决定下载量,由于我们的下载取决于当前的网速,所以在这种场合下更好的方案是
采用后者,这几天在闪存里面两次看到苍老师,肃然起敬,所以决定在不用线程和线程的情况下,看看下载仓老师的速度如何。
图片大小(217.27KB)
View Code
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Net; 6 using System.Threading; 7 using System.Threading.Tasks; 8 using System.IO; 9 using System.Collections.Concurrent; 10 using System.Diagnostics; 11 using System.Drawing; 12 13 14 namespace ConsoleApplication1 15 { 16 public class Program 17 { 18 public static CountdownEvent cde = new CountdownEvent(0); 19 20 //每个线程下载的字节数,方便最后合并 21 public static ConcurrentDictionary<long, byte[]> dic = new ConcurrentDictionary<long, byte[]>(); 22 23 //请求文件 24 public static string url = "http://www.pncity.net/bbs/data/attachment/forum/201107/30/1901108yyd8gnrs2isadrr.jpg"; 25 26 static void Main(string[] args) 27 { 28 for (int i = 0; i < 1; i++) 29 { 30 Console.WriteLine("\n****************************\n第{0}次比较\n****************************", (i + 1)); 31 32 //不用线程 33 //RunSingle(); 34 35 //使用多线程 36 RunMultiTask(); 37 } 38 39 Console.Read(); 40 } 41 42 static void RunMultiTask() 43 { 44 Stopwatch watch = Stopwatch.StartNew(); 45 46 //开5个线程 47 int threadCount = 5; 48 49 long start = 0; 50 51 long end = 0; 52 53 var total = GetSourceHead(); 54 55 if (total == 0) 56 return; 57 58 var pageSize = (int)Math.Ceiling((Double)total / threadCount); 59 60 cde.Reset(threadCount); 61 62 Task[] tasks = new Task[threadCount]; 63 64 for (int i = 0; i < threadCount; i++) 65 { 66 start = i * pageSize; 67 68 end = (i + 1) * pageSize - 1; 69 70 if (end > total) 71 end = total; 72 73 var obj = start + "|" + end; 74 75 tasks[i] = Task.Factory.StartNew(j => new DownFile().DownTaskMulti(obj), obj); 76 } 77 78 Task.WaitAll(tasks); 79 80 var targetFile = "C://" + url.Substring(url.LastIndexOf('/') + 1); 81 82 FileStream fs = new FileStream(targetFile, FileMode.Create); 83 84 var result = dic.Keys.OrderBy(i => i).ToList(); 85 86 foreach (var item in result) 87 { 88 fs.Write(dic[item], 0, dic[item].Length); 89 } 90 91 fs.Close(); 92 93 watch.Stop(); 94 95 Console.WriteLine("多线程:下载耗费时间:{0}", watch.Elapsed); 96 } 97 98 static void RunSingle() 99 { 100 Stopwatch watch = Stopwatch.StartNew(); 101 102 if (GetSourceHead() == 0) 103 return; 104 105 var request = (HttpWebRequest)HttpWebRequest.Create(url); 106 107 var response = (HttpWebResponse)request.GetResponse(); 108 109 var stream = response.GetResponseStream(); 110 111 var outStream = new MemoryStream(); 112 113 var bytes = new byte[10240]; 114 115 int count = 0; 116 117 while ((count = stream.Read(bytes, 0, bytes.Length)) != 0) 118 { 119 outStream.Write(bytes, 0, count); 120 } 121 122 var targetFile = "C://" + url.Substring(url.LastIndexOf('/') + 1); 123 124 FileStream fs = new FileStream(targetFile, FileMode.Create); 125 126 fs.Write(outStream.ToArray(), 0, (int)outStream.Length); 127 128 outStream.Close(); 129 130 response.Close(); 131 132 fs.Close(); 133 134 watch.Stop(); 135 136 Console.WriteLine("不用线程:下载耗费时间:{0}", watch.Elapsed); 137 } 138 139 //获取头信息 140 public static long GetSourceHead() 141 { 142 var request = (HttpWebRequest)HttpWebRequest.Create(url); 143 144 request.Method = "Head"; 145 request.Timeout = 3000; 146 147 var response = (HttpWebResponse)request.GetResponse(); 148 149 var code = response.StatusCode; 150 151 if (code != HttpStatusCode.OK) 152 { 153 Console.WriteLine("下载的资源无效!"); 154 return 0; 155 } 156 157 var total = response.ContentLength; 158 159 Console.WriteLine("当前资源大小为:" + total); 160 161 response.Close(); 162 163 return total; 164 } 165 } 166 167 public class DownFile 168 { 169 // 多线程下载 170 public void DownTaskMulti(object obj) 171 { 172 var single = obj.ToString().Split('|'); 173 174 long start = Convert.ToInt64(single.FirstOrDefault()); 175 176 long end = Convert.ToInt64(single.LastOrDefault()); 177 178 var request = (HttpWebRequest)HttpWebRequest.Create(Program.url); 179 180 request.AddRange(start, end); 181 182 var response = (HttpWebResponse)request.GetResponse(); 183 184 var stream = response.GetResponseStream(); 185 186 var outStream = new MemoryStream(); 187 188 var bytes = new byte[10240]; 189 190 int count = 0; 191 192 while ((count = stream.Read(bytes, 0, bytes.Length)) != 0) 193 { 194 outStream.Write(bytes, 0, count); 195 } 196 197 outStream.Close(); 198 199 response.Close(); 200 201 Program.dic.TryAdd(start, outStream.ToArray()); 202 203 Program.cde.Signal(); 204 } 205 } 206 }




在下面的图中可以看出,我们的资源被分成了n段,在217.27KB的情况下,多线程加速还不是很明显,我们可以试试更大的文件,这里我就
在本地放一个133M的rar文件。
//请求文件 public static string url = "http://localhost:56933/1.rar";
现在看一下效果是非常明显的。

日常开发中,相信大家经常会用like去匹配一些数据,同时我们也知道,like往往会导致全表扫描,当数据量越来越大的时候,我们会纠结于
数据库的龟速查找,此时我们必须另寻蹊跷,这时lucene就可以大显身手了。
首先我们做一个demo,向数据库中插入10w条数据,总共778M。

接下来,我们搜索下新闻内容中包含“流行”的记录。

mmd,检索一下要78s,是谁都要砸了面前的破机子。
下面我们来看看lucene的效果怎么样。下载地址:http://incubator.apache.org/lucene.net/download.html
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using Lucene.Net.Index; 6 using Lucene.Net.Store; 7 using Lucene.Net.Analysis.Standard; 8 using Lucene.Net.Documents; 9 using System.Data; 10 using System.Diagnostics; 11 using Lucene.Net.Search; 12 13 using Lucene.Net.QueryParsers; 14 15 namespace First 16 { 17 class Program 18 { 19 static string path = @"D:\Sample"; 20 21 static void Main(string[] args) 22 { 23 //创建索引 24 CreateIndex(); 25 26 var watch = Stopwatch.StartNew(); 27 28 //搜索 29 IndexSearcher search = new IndexSearcher(path); 30 31 //查询表达式 32 QueryParser query = new QueryParser(string.Empty, new StandardAnalyzer()); 33 34 //query.parse:注入查询条件 35 var hits = search.Search(query.Parse("Content:流行")); 36 37 for (int i = 0; i < hits.Length(); i++) 38 { 39 Console.WriteLine("当前内容:{0}", hits.Doc(i).Get("Content").Substring(0, 20) + "..."); 40 } 41 42 watch.Stop(); 43 44 Console.WriteLine("搜索耗费时间:{0}", watch.ElapsedMilliseconds); 45 } 46 47 static void CreateIndex() 48 { 49 //创建索引库目录 50 var directory = FSDirectory.GetDirectory(path, true); 51 52 //创建一个索引,采用StandardAnalyzer对句子进行分词 53 IndexWriter indexWriter = new IndexWriter(directory, new StandardAnalyzer()); 54 55 var reader = DbHelperSQL.ExecuteReader("select * from News"); 56 57 while (reader.Read()) 58 { 59 //域的集合:文档,类似于表的行 60 Document doc = new Document(); 61 62 //要索引的字段 63 doc.Add(new Field("ID", reader["ID"].ToString(), Field.Store.YES, Field.Index.NOT_ANALYZED)); 64 doc.Add(new Field("Title", reader["Title"].ToString(), Field.Store.NO, Field.Index.ANALYZED)); 65 doc.Add(new Field("Content", reader["Content"].ToString(), Field.Store.YES, Field.Index.ANALYZED)); 66 67 indexWriter.AddDocument(doc); 68 } 69 70 reader.Close(); 71 72 //对索引文件进行优化 73 indexWriter.Optimize(); 74 75 indexWriter.Close(); 76 } 77 } 78 }

我靠,448ms,顿时78s黯然失色,当然这个时间是不包含"创建索引“的时间,从时间复杂度上来说,这种预加载索引算是常量。
作为入门,简单的介绍下lucene的实现过程,首先lucene主要分成两步:"索引"和"搜索"。
一:索引:
相信大家对索引还是比较熟悉的,lucene能够将我们内容切分成很多词,然后将词作为key,建立“倒排索引”,然后放到索引库中,在上面
的例子中,我们看到了索引过程中使用到了IndexWriter,FSDirectory,StandardAnalyzer,Document和Field这些类,下面简要分析下。
1:IndexWriter
我们看到该类有一个AddDocument方法,所以我们认为该类实现了索引的写入操作。
2:FSDirectory
这个就更简单了,提供了索引库的存放位置,比如我们这里的D:\Sample,或许有人问,能不能存放在内存中,在强大的lucene面前当然
可以做到,lucene中的RAMDirectory就可以实现,当然我们的内存足够大的话,还是可以用内存承载索引库,进而提高搜索的效率。
3:StandardAnalyzer
这个算是索引过程中最最关键的一步,也是我们使用lucene非常慎重考虑的东西,之所以我们能搜索秒杀,关键在于我们如何将输入的内容
进行何种形式的切分,当然不同的切分形式诞生了不同的分析器,StandardAnalyzer就是一个按照单字分词的一种分析器,详细的介绍后续文
章分享。
4:Document
在上面的例子可以看到,他是承载field的集合,然后添加到IndexWriter中,有点类似表中的行的概念。
5: Field
提供了对要分析的字段进行何种处理,以KV形式呈现。
①:Field.Store.YES, Field.Index.NOT_ANALYZED 表示对索引字段采取:原样保存并且不被StandardAnalyzer进行切分。
②: Field.Store.NO, Field.Index.ANALYZED 不保存但是要被StandardAnalyzer切分。
二:搜索
这个比较容易,根据我们输入的词lucene能够在索引库中快速定位到我们要找的词,同样我们可以看到IndexSearcher,QueryParser,Hits。
1:IndexSearcher
这个我们可以理解成以只读的形式打开由IndexWriter创建的索引库,search给QueryParser提供了查询的桥梁。
2:QueryParser
这玩意提供了一个parse方法能够将我们要查找的词转化为lucene能够理解了查询表达式。
3:Hits
这个就是获取匹配结果的一个指针,优点类似C#中的延迟加载,目的都是一样,提高性能。
好了,大体上也就这样,时间不早了,洗洗睡了。嘻嘻。
承接上一篇,我们继续说下.net4.0中的同步机制,是的,当出现了并行计算的时候,轻量级别的同步机制应运而生,在信号量这一块
出现了一系列的轻量级,今天继续介绍下面的3个信号量 CountdownEvent,SemaphoreSlim,ManualResetEventSlim。
一:CountdownEvent
这种采用信号状态的同步基元非常适合在动态的fork,join的场景,它采用“信号计数”的方式,就比如这样,一个麻将桌只能容纳4个
人打麻将,如果后来的人也想搓一把碰碰运气,那么他必须等待直到麻将桌上的人走掉一位。好,这就是简单的信号计数机制,从技术角
度上来说它是定义了最多能够进入关键代码的线程数。
但是CountdownEvent更牛X之处在于我们可以动态的改变“信号计数”的大小,比如一会儿能够容纳8个线程,一下又4个,一下又10个,
这样做有什么好处呢?还是承接上一篇文章所说的,比如一个任务需要加载1w条数据,那么可能出现这种情况。
加载User表: 根据user表的数据量,我们需要开5个task。
加载Product表: 产品表数据相对比较多,计算之后需要开8个task。
加载order表: 由于我的网站订单丰富,计算之后需要开12个task。
先前的文章也说了,我们需要协调task在多阶段加载数据的同步问题,那么如何应对这里的5,8,12,幸好,CountdownEvent给我们提供了
可以动态修改的解决方案。
1 using System.Collections.Concurrent;
2 using System.Threading.Tasks;
3 using System;
4 using System.Diagnostics;
5 using System.Collections.Generic;
6 using System.Linq;
7 using System.Threading;
8
9 class Program
10 {
11 //默认的容纳大小为“硬件线程“数
12 static CountdownEvent cde = new CountdownEvent(Environment.ProcessorCount);
13
14 static void Main(string[] args)
15 {
16 //加载User表需要5个任务
17 var userTaskCount = 5;
18
19 //重置信号
20 cde.Reset(userTaskCount);
21
22 for (int i = 0; i < userTaskCount; i++)
23 {
24 Task.Factory.StartNew((obj) =>
25 {
26 LoadUser(obj);
27 }, i);
28 }
29
30 //等待所有任务执行完毕
31 cde.Wait();
32
33 Console.WriteLine("\nUser表数据全部加载完毕!\n");
34
35 //加载product需要8个任务
36 var productTaskCount = 8;
37
38 //重置信号
39 cde.Reset(productTaskCount);
40
41 for (int i = 0; i < productTaskCount; i++)
42 {
43 Task.Factory.StartNew((obj) =>
44 {
45 LoadProduct(obj);
46 }, i);
47 }
48
49 cde.Wait();
50
51 Console.WriteLine("\nProduct表数据全部加载完毕!\n");
52
53 //加载order需要12个任务
54 var orderTaskCount = 12;
55
56 //重置信号
57 cde.Reset(orderTaskCount);
58
59 for (int i = 0; i < orderTaskCount; i++)
60 {
61 Task.Factory.StartNew((obj) =>
62 {
63 LoadOrder(obj);
64 }, i);
65 }
66
67 cde.Wait();
68
69 Console.WriteLine("\nOrder表数据全部加载完毕!\n");
70
71 Console.WriteLine("\n(*^__^*) 嘻嘻,恭喜你,数据全部加载完毕\n");
72
73 Console.Read();
74 }
75
76 static void LoadUser(object obj)
77 {
78 try
79 {
80 Console.WriteLine("当前任务:{0}正在加载User部分数据!", obj);
81 }
82 finally
83 {
84 cde.Signal();
85 }
86 }
87
88 static void LoadProduct(object obj)
89 {
90 try
91 {
92 Console.WriteLine("当前任务:{0}正在加载Product部分数据!", obj);
93 }
94 finally
95 {
96 cde.Signal();
97 }
98 }
99
100 static void LoadOrder(object obj)
101 {
102 try
103 {
104 Console.WriteLine("当前任务:{0}正在加载Order部分数据!", obj);
105 }
106 finally
107 {
108 cde.Signal();
109 }
110 }
111 }

我们看到有两个主要方法:Wait和Signal。每调用一次Signal相当于麻将桌上走了一个人,直到所有人都搓过麻将wait才给放行,这里同样要
注意也就是“超时“问题的存在性,尤其是在并行计算中,轻量级别给我们提供了”取消标记“的机制,这是在重量级别中不存在的,比如下面的
重载public bool Wait(int millisecondsTimeout, CancellationToken cancellationToken),具体使用可以看前一篇文章的介绍。
二:SemaphoreSlim
在.net 4.0之前,framework中有一个重量级的Semaphore,人家可以跨进程同步,咋轻量级不行,msdn对它的解释为:限制可同时访问
某一资源或资源池的线程数。关于它的重量级demo,我的上一个系列有演示,你也可以理解为CountdownEvent是 SemaphoreSlim的功能加
强版,好了,举一个轻量级使用的例子。
1 using System.Collections.Concurrent;
2 using System.Threading.Tasks;
3 using System;
4 using System.Diagnostics;
5 using System.Collections.Generic;
6 using System.Linq;
7 using System.Threading;
8
9 class Program
10 {
11 static SemaphoreSlim slim = new SemaphoreSlim(Environment.ProcessorCount, 12);
12
13 static void Main(string[] args)
14 {
15 for (int i = 0; i < 12; i++)
16 {
17 Task.Factory.StartNew((obj) =>
18 {
19 Run(obj);
20 }, i);
21 }
22
23 Console.Read();
24 }
25
26 static void Run(object obj)
27 {
28 slim.Wait();
29
30 Console.WriteLine("当前时间:{0}任务 {1}已经进入。", DateTime.Now, obj);
31
32 //这里busy3s中
33 Thread.Sleep(3000);
34
35 slim.Release();
36 }
37 }

同样,防止死锁的情况,我们需要知道”超时和取消标记“的解决方案,像SemaphoreSlim这种定死的”线程请求范围“,其实是降低了扩展性,
所以说,试水有风险,使用需谨慎,在觉得有必要的时候使用它。
三: ManualResetEventSlim
相信它的重量级别大家都知道是ManualReset,而这个轻量级别采用的是"自旋等待“+”内核等待“,也就是说先采用”自旋等待的方式“等待,
直到另一个任务调用set方法来释放它。如果迟迟等不到释放,那么任务就会进入基于内核的等待,所以说如果我们知道等待的时间比较短,采
用轻量级的版本会具有更好的性能,原理大概就这样,下面举个小例子。
1 using System.Collections.Concurrent;
2 using System.Threading.Tasks;
3 using System;
4 using System.Diagnostics;
5 using System.Collections.Generic;
6 using System.Linq;
7 using System.Threading;
8
9 class Program
10 {
11 //2047:自旋的次数
12 static ManualResetEventSlim mrs = new ManualResetEventSlim(false, 2047);
13
14 static void Main(string[] args)
15 {
16
17 for (int i = 0; i < 12; i++)
18 {
19 Task.Factory.StartNew((obj) =>
20 {
21 Run(obj);
22 }, i);
23 }
24
25 Console.WriteLine("当前时间:{0}我是主线程{1},你们这些任务都等2s执行吧:\n",
26 DateTime.Now,
27 Thread.CurrentThread.ManagedThreadId);
28 Thread.Sleep(2000);
29
30 mrs.Set();
31
32 Console.Read();
33 }
34
35 static void Run(object obj)
36 {
37 mrs.Wait();
38
39 Console.WriteLine("当前时间:{0}任务 {1}已经进入。", DateTime.Now, obj);
40 }
41 }


