Chanel和goroutine机制用C#完成GoogleGo语言表达中的Chanel和goroutine
前段时间试了一点 Google 的 Go 语言表达,感觉很多其他特点都很好。Go 语言表达致力于结合传统编译程序静态语言和解释动态语言的优势,找到平衡。从而创造出快速(编译执行)、省力的语言表达(动态语言通常简单方便)。与此同时,Go 语言表达还具有促进并发编程的丰富特点,这在当今多核非常流行的情况下是一个非常重要和强大的功能。
Go 语言表达的高并发特征主要包括 goroutine, channel 等。goroutine - 它可以大致解释为一个轻量级的过程(或微过程),它是一个“可并行执行的函数在同一详细地址空间内分配”。同时,它是轻量级的,不需要像分配过程一样分配单独的堆栈空间。因此,理论上,我们可以很容易地分配多个 goroutine, 并发执行,成本远小于线程同步程序流程,从而使程序流程适用于更大的并发性。
channel - 说白了,就是安全通道。安全通道的目的是传输数据。每个人都可以在一个通道上发送数据信息(Send)和理解(Receive)实际操作。对非缓存的 channel 来讲,Receive 在执行该方法时,将判断安全通道上是否有值,如果没有,将等待(堵塞),直到有值。一样,在 channel 上面有值,而被一个 Receiver 接纳时,Send 方法也会被堵塞,直到 Channel 变空。这样,就可以根据一个简单的系统来保证 Send 和 Receive 它总是在不同的时间实施,只有 Send 后才能 Receive. 这避免了常规多线程编程中的信息共享问题。如同 Go 语言表达文本文档一句话常说:
Do not communicate by sharing memory; instead, share memory by communicating.
不需要根据共享内存进行沟通;而是通过沟通共享内存。
在常规的多线程编程中,人们总是定义一些类变量。如果多个线程可以同时浏览自变量,则需要锁定。这样就带来了一定程序编写的多样性,如果编码中写的稍有bug,就会导致读/提到不正确的数值。
并且通过 channel 为了沟通,我们得到了一种更清晰的沟通方式。两个过程(或 goroutine)如果你想读写能力相同的数据信息,你应该创建一个安全通道。双方通过这个安全通道实施 Send / Receive 操作可以设置值或选择值,相对来说,不容易出错。
为了更深入地理解这一原则,我试过了 C# 获得类似的效果。
相较于 goroutine, 我没有完成微过程,因为这必须是一个更复杂的调度机制(准备进一步研究这些方面)。我们可以临时使用 Thread 简单模拟模拟。
而 Channel, 则以 Semaphone 操纵同步 Send / Receive 就行了。
首先,让我们完成一个简单的工作 Channel,上面已经说过概念:
view sourceprint?01 ///
02 /// 先完成简单,没有缓存。 Channel.
03 ///
04 ///
05 public class Channel
06 {
07 T _value;
08
09 // 逐渐不可以 Receive.
10 Semaphore _canReceive = new Semaphore(0, 1);
11
12 // 渐渐没有价值,可以 Send
13 Semaphore _canSend = new Semaphore(1, 1);
14
15 public T Receive()
16 {
17 // 等候有值
18 _canReceive.WaitOne();
19
20 T value = _value;
21
22 // 一个新的通知值得推送
23 _canSend.Release();
24
25 return value;
26 }
27
28 public void Send(T value)
29 {
30 // 若为非缓存现象,它是堵塞式的,需要等待已经有数值的一个 Receiver 接纳完,
31 // 只有这样,才能推送新值,不可以持续 Send
32 _canSend.WaitOne();
33 _value = value;
34
35 // 可以接收通知
36 _canReceive.Release();
37 }
38 }
下面粗略的模拟模拟完成 goroutine 的词法:
view sourceprint?01 public static class GoLang
02 {
03 ///
04 /// 首先,简单地使用过程模拟 goroutine. 由于使用 channel 通讯,因此
05 /// 过程之间的信息共享/同步问题不应考虑
06 ///
07 ///
08 public static void go(Action action)
09 {
10 new Thread(new ThreadStart(action)).Start();
11 }
12
13 }
有这些,我们可以写一个 test case 来检验了。下面的代码可以简单地创建并发代码 routine,各自做整数金额 send, receive 实际操作,以验证正确的发送和理解值:
view sourceprint?01 ///
02 /// 检测好几个 Sender 好几个 Receiver 同时在一个 channel 推送/接收信息
03 ///
04 private static void Test1()
05 {
06 var ch = new Channel
07
08 // 运行好几个 Sender
09 GoLang.go(() =>
10 {
11 var id = Thread.CurrentThread.ManagedThreadId;
12 for (var i = 0; i < 7; i )
13 {
14 Thread.Sleep(new Random((int)DateTime.Now.Ticks).Next(3000));
15 Console.WriteLine(线程{0}推送值: {1}", id, i);
16 ch.Send(i);
17 }
18 });
19
< 7; i )13 {
14 Thread.Sleep(new Random((int)DateTime.Now.Ticks).Next(3000));
15 Console.WriteLine('线程{0}推送值: {1}', id, i);
16 ch.Send(i);
17 }
18 });
19
20 GoLang.go(() =>
21 {
22 var id = Thread.CurrentThread.ManagedThreadId;
23 for (var i = 7; i < 15; i )
24 {
25 Thread.Sleep(new Random((int)DateTime.Now.Ticks).Next(3000));
26 Console.WriteLine(线程{0}推送值: {1}", id, i);
27 ch.Send(i);
28 }
29 });
30
31 // 运行好几个 Receiver
< 15; i )24 {
25 Thread.Sleep(new Random((int)DateTime.Now.Ticks).Next(3000));
26 Console.WriteLine('线程{0}推送值: {1}', id, i);
27 ch.Send(i);
28 }
29 });
30
31 // 运行好几个 Receiver
32 GoLang.go(() =>
33 {
34 var id = Thread.CurrentThread.ManagedThreadId;
35 for (var i = 0; i < 5; i )
36 {
37 //Console.WriteLine(线程{0}堵塞”, id);
38 var value = ch.Receive();
39 Console.WriteLine(线程{0}得到值: {1}", id, value);
40 }
41 });
42
< 5; i )36 {
37 //Console.WriteLine('线程{0}堵塞', id);
38 var value = ch.Receive();
39 Console.WriteLine('线程{0}得到值: {1}', id, value);
40 }
41 });
42
43 GoLang.go(() =>
44 {
45 var id = Thread.CurrentThread.ManagedThreadId;
46 for (var i = 0; i < 5; i )
47 {
48 //Console.WriteLine(线程{0}堵塞”, id);
49 var value = ch.Receive();
50 Console.WriteLine(线程{0}得到值: {1}", id, value);
51 }
52 });
53
< 5; i )47 {
48 //Console.WriteLine('线程{0}堵塞', id);
50 Console.WriteLine('线程{0}得到值: {1}', id, value);
51 }
52 });
54 GoLang.go(() =>
55 {
57 for (var i = 0; i < 5; i )
60 var value = ch.Receive();
61 Console.WriteLine(线程{0}得到值: {1}", id, value);
62 }
63 });
64 }
再试一次 Go 语言表达文档中列出的一个例子 - 筛法要素数:
(见:http://golang.org/doc/go_tutorial.html, Prime numbers)
view sourceprint?01 public class PrimeNumbers
02 {
03 public voidMain()
04 {
05 var primes = Sieve();
06
07 // 检测:打印前100个素数
08 for (var i = 0; i < 100; i )
09 {
10 Console.WriteLine(primes.Receive());
12 }
13
14 ///
15 /// 筛法求素数
18 Channel
20 var @out = new Channel
();
21 GoLang.go(() =>
22 {
23 var ch = Generate();
24 for (; ; )
25 {
26 // 现阶段编码序列中的第一个值一直是素数
27 var prime = ch.Receive();
28
30 @out.Send(prime);
31
32 // 用这个素数处理目录,进入下一个循环系统可以确保至少第一个数是素数
33 ch = Filter(ch, prime);
36 return @out;
39 ///
40 /// 自然数的无限编码序列从2开始,这也是初始等差数列
41 /// 其逐渐原素 2 是素数。
42 ///
43 ///
44 Channel
Generate()
45 {
46 var ch = new Channel
();
47 GoLang.go(() =>
48 {
49 for (var i = 2; ; i )
50 {
51 ch.Send(i);
52 }
53 });
54 return ch;
55 }
56
57 ///
58 /// 从键入 channel 逐一阅读选值,将无法被 prime 能整除
59 /// 这些发送到导出 channel (既用 prime 对 @in 一次选择序列)
60 /// 61 Channel Filter(Channel @in, int prime) 62 { 63 var @out = new Channel();
64 GoLang.go(() => 65 { 66 for (; ; ) 67 { 68 var i = @in.Receive(); 69 if (i % prime != 0) 70 { 71 @out.Send(i); 72 } 73 } 74 }); 75 return @out; 76 } 77 } 以下是所有检测项目 Main 方式: view sourceprint?01 class Program 02 { 03 static void Main(string[] args) 04 { 05 Test1(); 06 07 new PrimeNumbers().Main(); 08 09 Console.ReadLine(); 10 } 11 } 因为代码中已经详细注解了,不要多做表达。可见,可用 Channel 这个概念(仿佛和 Reactive Programming 有点关系?可见,可用 Channel 这个概念(仿佛和 Reactive Programming 有点关系吗?),我们可以更清楚地构建线程同步或高并发应用程序。