控制台研究

本文探讨了控制台作为双向通信设备的内部机制,重点分析了控制台输入输出流的工作原理,包括原子性写操作、多线程下读写行为及阻塞特性。

控制台类似于socket,是由两个流组成的一个复合对象。“由两个流组成的复合对象”初看起来有点奇怪,仔细研究发现计算机中凡是实现双向通信的装置大都如此。流是单向的,是一端读取另一端的数据,或者说是另一端向一端写入数据。这只能实现单向的通信,成为单工。如果有了正反两个方向的流,就构成了一个双向通信的装置,成为双工。并且这个装置可以实现数据同时在两个方向上流动。

还有一种通信通道被称为“半双工”,虽然也可以实现双向通信,但是只能是由双方分时复用一条通信线路,即不能同时。我觉得计算机在底层实现半双工反而比双工困难。在高层应用可以找到一些半双工模式的通信,例如HTTP。这些模式好像在底层都是由双工实现的,只是放弃了同时双向通信的特质。

控制台的Write方法是原子性的,不会被其他线程所中断。我认为控制台的这一特性为使用控制台进行多线程测试奠定了基础。这一点的不到保证的话控制台在多线程下的输出将十分可怕。

当线程执行到Read系列方法的时候,如果流中有字符,将读取出来,并将该字符echo到控制台的输出流;如果读取完毕则返回,如果没有读取完毕就继续读去。如果读取时输入流中没有任何数据,那么线程将被阻塞。这就是为什么我们输入字符的时候,这些字符会被回显到显示器上。Echo写只是普通的对控制台的写,如果此时控制台正在执行大量的Write方法调用,那么Echo字符将与这些调用以未指定的方式夹杂在一起。

这里一个令人困惑的地方是,如果程序已经阻塞,为什么控制台还能接受按键呢?我认为,控制台是操作系统中的一个装置,是由操作系统维护的,而我们的程序只是和这个装置打交道而已。虽然我们的程序阻塞了,但是操作系统是能够正常响应的。因此当用户有了特定的输入是,操作系统向这个控制台的输入流写入数据,从而解除了我们程序的阻塞状态。这也是为什么我们的程序在不断的输出过程中(while(true)Console.WriteLine(“xxx”);)能够响应break键的操作。控制台的输入流和输出流都是独占性资源,必须获取其锁才能访问。而当按下break键,操作系统将尽快回收控制台的锁,从而使程序中的Write方法阻塞。

同样可知,Console.Read系列方法也是要独占控制台的输入流才能执行的。因此多个线程的Console.Read方法同时执行,一次只有一个线程能够获得输入。例如如果是ReadLine方法的话,将只有一个线程能够从控制台读取一行数据。

Test:

Code
 1using System;
 2using System.Text;
 3using System.Threading;
 4
 5namespace ThreadTest
 6{
 7
 8    public class Test2
 9    {
10        /**//*
11         * 测试对控制台的输入和输出的关系。
12         * Write是从控制台输出
13         * Read是从控制台输入
14         * 一个线程向控制台的输出不会成为另一个线程从控制台的输入
15         * 可以将 Console与Socket类比,包含了一个输入流和一个输出流
16         * 这两个流都可以被多个线程访问
17         */

18        run1#region run1
19        private void Thread1Method()
20        {
21            string s = Console.ReadLine();
22            Console.WriteLine("Thread1Method recived:" + s);
23        }

24        private void Thread2Method()
25        {
26            Console.WriteLine("Thread2 Output");
27        }

28
29        public void Run()
30        {
31            new Thread(new ThreadStart(Thread1Method)).Start();
32
33            Thread.Sleep(1000);
34
35            new Thread(new ThreadStart(Thread2Method)).Start();
36
37            Thread.Sleep(10000);
38        }

39        #endregion

40
41
42        /**//*
43         * Console的工作原理
44         * Console在每次输入流(不重定向的话就是键盘)输入的时候向输出流
45         * 写下输入的这个字符,这被称为“echo”
46         * 因此当一个线程不停的输出字符时,echo写入将混迹于其中
47         * 但Console是能够正常接收到这个字符的
48         * 也就是说,敲键盘和屏幕显示字符本来就是两件不相关的事,是echo功能把两个东西联系在了一起
49         * 要证实这个理论,可以以很快的速度敲一个字符,然后按break键,向上拉滚动条就能够看到echo字符和write方法输出的字符混在一块
50         * 那么break是什么原理?
51         * 这个不太重要,所以提一个假设就不再尝试了:
52         * Write需要获取一个锁才能写,而Break键能够获取这个锁,从而阻塞了所有的Write方法
53         * 
54         */

55        Run2#region Run2
56
57        public void Run2()
58        {
59            new Thread(new ThreadStart(Output)).Start();
60
61            new Thread(new ThreadStart(Input)).Start();
62
63            Thread.Sleep(30000);
64        }

65
66        private void Output()
67        {
68            while (true)
69            {
70                lock (syncObj)
71                {
72                    Console.WriteLine("The Billie Jean is not my lover..");
73                }

74            }

75        }

76
77        private void Input()
78        {
79            Console.Write("Input it:");
80            string s = Console.ReadLine();
81            lock (syncObj)
82            {
83                Console.Write("You inputed " + s);
84                Console.Read();
85            }

86
87        }

88
89        private object syncObj = new object();
90
91        #endregion

92    }

93}

94

 

Code
 1using System;
 2using System.Text;
 3using System.Threading;
 4
 5namespace ThreadTest
 6{
 7    //测试控制台输出的原子性
 8    //Write方法是原子的不会被其他线程的Write中断
 9    class Test1
10    {
11        private const int MessageLength = 1000;
12
13        public void Run()
14        {
15            Thread thread1 = new Thread(new ThreadStart(Output1));
16            Thread thread2 = new Thread(new ThreadStart(Output2));
17
18            thread1.Start();
19            thread2.Start();
20
21            Thread.Sleep(30000);
22
23        }

24
25        private void Output1()
26        {
27            Output('1');
28        }

29
30        private void Output2()
31        {
32            Output('2');
33        }

34
35        private void Output(char c)
36        {
37            StringBuilder sb = new StringBuilder(MessageLength);
38            for (int i = 0; i < MessageLength; i++)
39            {
40                sb.Append(c);
41            }

42            string message = sb.ToString();
43            for (int i = 0; i < 100; i++)
44            {
45                Console.WriteLine(message);
46            }

47        }

48
49    }

50}

51

 

 

Code
 1using System;
 2using System.Collections.Generic;
 3using System.Linq;
 4using System.Text;
 5using System.Threading;
 6
 7namespace ThreadTest
 8{
 9    public class Test3
10    {
11        /**//*
12         * 多个线程同时读取输入流也是要排队的
13         * 或者说有锁的,最先到达(read/readline)的线程锁定了控制台的读取流
14         * 然后读取到数据后释放读取流锁
15         * 其它的线程才能够从控制台读取东西
16         */

17        public void Run()
18        {
19            new Thread(new ThreadStart(Thread1Method)).Start();
20            new Thread(new ThreadStart(Thread2Method)).Start();
21
22            Thread.Sleep(30000);
23        }

24
25        private void Thread1Method()
26        {
27            string s = Console.ReadLine();
28            Console.WriteLine("Thread1->Just inputed: " + s);
29        }

30
31        private void Thread2Method()
32        {
33            string s = Console.ReadLine();
34            Console.WriteLine("Thread2->Just inputed: " + s);
35        }

36
37        /**//*
38         * 这个实验是为了测试为什使用一个线程时,主线程阻塞后按键的Echo没有了
39         * 这是因为,Echo虽然是控制台本身的性质,但是必须借助一定的契机才能输出
40         * 当只使用一个线程时,而这个线程处于阻塞状态,按下任何字符好像都没有反应
41         * 但这是一个假象,因为控制台是操作系统的设施,只要操作系统还能响应,这些输入就都已经进入输入流中
42         * 但是由于所有线程都处于阻塞状态,这些输入的字符没法得到处理
43         * 直到有ReadLine或者Read方法调用
44         * 当调用ReadLine方法的时候,如果控制台的Echo属性为True,那么就进入读取-打印循环,直到遇到换行符为止
45         * ReadLine方法每从输入流中读取一个字符后,都会把这个字符Write出来这是控制台Echo属性的要求
46         */

47        public void Run2()
48        {
49            Thread.Sleep(5000);
50            Console.WriteLine("fff");
51            //string s = Console.ReadLine();
52            //Console.Write("ECHO:" + s);
53            //Console.Read();
54            Thread.Sleep(5000);
55        }

56    }

57}

58

 

 

转载于:https://www.cnblogs.com/zhy2002/archive/2008/06/25/1230042.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值