当前位置: 移动技术网 > IT编程>开发语言>c# > 同步异步多线程这三者关系,你能给面试官一个满意的回答吗?

同步异步多线程这三者关系,你能给面试官一个满意的回答吗?

2020年04月21日  | 移动技术网IT编程  | 我要评论

前几天一位朋友去面试,面试官问了他同步,异步,多线程之间是什么关系,异步比同步高效在哪?多线程比单线程高效在哪?由于回答的不好,让我帮他捋一下,其实回答这个问题不难,难就难在只对别人说理论,而没有现杀的例子。

一:异步

1. 到底解放了谁?

<1> 从基础的同步说起

要说解放了谁,一定得有几个参与者,举个例子:当你的主线程读取一个应用程序之外的资源时,它有可能是一个文件,又有可能是一个外部服务,当用同步方式读取外部服务时,首先主线程会从用户模式进入到内核模式,在内核模式中windows会将你的请求数据交给对应的网络驱动程序,继后会让这个线程进入休眠状态,当网络驱动程序和外部服务一阵痉挛之后,网络驱动程序会将获取到的结果交给当初休眠的线程,windows唤醒休眠线程继而执行后续的c#代码,画个简图理解一下,不一定全对。

这里就存在着一个非常大的问题,步骤4-步骤7之间,你的主线程一直都是休眠状态,比如在gui编程中,有一个重要的原则就是解放你的ui线程(主线程),所以解决这个问题就迫在眉睫。

<2> 异步方式下的处理方案

说到这里,大家应该知道了异步方式就是为了解放主线程,又可以叫调用线程,没错,接下来看一下同样的场景在异步中如何处理的。

从图中可以看到,步骤三中将thread数据交给网络驱动程序之后,该thread就直接返回不管了,当后续网络驱动程序获取数据后,将数据丢给clr线程池中的io线程再由它触发你的回调函数。

<3> 总结

异步相比同步效率高就高在解放了调用线程,在驱动程序和远程服务roundtrip期间,调用线程还可以执行其他工作,放在gui上就是主线程可以继续响应用户的超敏操作。

由于没有空转的线程,cpu可以得到最满载的运转,更少的线程就有更少的线程栈空间,更少的gc回收时间和上下文切换。

2. 代码演示

还是那句话,光说可不行,你得上一点代码看看,有了上面的理论基础,这里我就模拟爬取下博客园首页的所有文章的用户头像。

<1> 同步代码

        public static void main(string[] args)
        {
            singlethreaddownloadimages();
            console.writeline("主线程继续执行其他的咯~~~");
            console.read();
        }

        public static void singlethreaddownloadimages()
        {
            using (var client = new httpclient())
            {
                //调用线程 空转等待。。。
                var content = client.getstringasync("http://cnblogs.com").result;
                var html = new htmldocument();
                html.loadhtml(content);

                var imgsrclist = html.documentnode.queryselectorall("img.pfs").select(m => m.attributes["src"].value)
                                                  .tolist();

                console.writeline($"准备下载:{imgsrclist.count}个...");

                for (int i = 0; i < imgsrclist.count; i++)
                {
                    //调用线程 空转等待。。。
                    var stream = client.getstreamasync(imgsrclist[i]).result;

                    image.fromstream(stream).save($@"c:\2\{i}.jpg");
                }
            }
            console.writeline("singlethreaddownloadimages 执行结束");
        }

------ output ------

准备下载:19个...
singlethreaddownloadimages 执行结束
主线程继续执行其他的咯~~~

<2> 异步代码

        public static void main(string[] args)
        {
            asyncdownloadimages();
            console.writeline("主线程继续执行其他的咯~~~");
            console.read();
        }

        public static async void asyncdownloadimages()
        {
            using (var client = new httpclient())
            {
                var content = await client.getstringasync("http://cnblogs.com");
                var html = new htmldocument();
                html.loadhtml(content);

                var imgsrclist = html.documentnode.queryselectorall("img.pfs").select(m => m.attributes["src"].value)
                                                  .tolist();

                console.writeline($"准备下载:{imgsrclist.count}个...");

                for (int i = 0; i < imgsrclist.count; i++)
                {
                    var stream = await client.getstreamasync(imgsrclist[i]);
                    image.fromstream(stream).save($@"c:\2\{i}.jpg");
                }

                console.writeline("asyncdownloadimages 执行结束");
            }
        }

------ output ------

主线程继续执行其他的咯~~~
准备下载:19个...
asyncdownloadimages 执行结束

从结果可以看出,异步在获取图片期间,主线程还可以做其他事情,这就是异步最大的特点。

3. windbg 提取是否真为线程池io线程

其实在图2中我口口声声的说是线程池中的io线程回调了你的函数,大家先要明白一个概念,线程池中有两种类别的线程,一个是工作线程,一个是io线程,而工作线程常常就是我们通过代码进行操控,io线程通常由底层clr接管,常常用于处理外部资源的操作,如下threadpool的getmaxthreads方法。

    public static void getmaxthreads(out int workerthreads, out int completionportthreads);

有了这个基础,再将 asyncdownloadimages方法修改如下,抓取一下dump文件

                var content = await client.getstringasync("http://cnblogs.com");
                console.writeline($"已获取到:{content.length}个字符");
                console.readline();

~*e !clrstack 查看所有托管线程的调用堆栈

0:000>  ~*e    !clrstack
os thread id: 0x62d8 (13)
        child sp               ip call site
000000da9b1fd1e8 00007ff9fc7bb4f4 [gcframe: 000000da9b1fd1e8] 
000000da9b1fd308 00007ff9fc7bb4f4 [gcframe: 000000da9b1fd308] 
000000da9b1fd368 00007ff9fc7bb4f4 [helpermethodframe_1obj: 000000da9b1fd368] system.threading.monitor.enter(system.object)
000000da9b1fd460 00007ff9e42f8aff system.io.textreader+synctextreader.readline()
000000da9b1fd4c0 00007ff9e40f0d98 system.console.readline()
000000da9b1fd4f0 00007ff985c81559 consoleapp2.program+d__3.movenext() [c:\dream\csharp\consoleapp1\consoleapp2\program.cs @ 93]
000000da9b1fd690 00007ff9e388cef2 system.threading.executioncontext.runinternal(system.threading.executioncontext, system.threading.contextcallback, system.object, boolean)
000000da9b1fd760 00007ff9e388cd75 system.threading.executioncontext.run(system.threading.executioncontext, system.threading.contextcallback, system.object, boolean)
000000da9b1fd790 00007ff9e38fbe2f system.runtime.compilerservices.asyncmethodbuildercore+movenextrunner.run()
000000da9b1fd7e0 00007ff9e3901343 system.threading.tasks.awaittaskcontinuation.runorscheduleaction(system.action, boolean, system.threading.tasks.task byref)
000000da9b1fd830 00007ff9e3865f40 system.threading.tasks.task.finishcontinuations()
000000da9b1fd8c0 00007ff9e3865a88 system.threading.tasks.task`1[[system.__canon, mscorlib]].trysetresult(system.__canon)
000000da9b1fd900 00007ff9e3865a05 system.threading.tasks.taskcompletionsource`1[[system.__canon, mscorlib]].trysetresult(system.__canon)
000000da9b1fd940 00007ff9c88311a3 system.net.http.httpclient+c__displayclass31_0`1[[system.__canon, mscorlib]].b__1(system.threading.tasks.task`1)
000000da9b1fd990 00007ff9e38f9d47 system.threading.tasks.task.execute()
000000da9b1fd9d0 00007ff9e388cef2 system.threading.executioncontext.runinternal(system.threading.executioncontext, system.threading.contextcallback, system.object, boolean)
000000da9b1fdaa0 00007ff9e388cd75 system.threading.executioncontext.run(system.threading.executioncontext, system.threading.contextcallback, system.object, boolean)
000000da9b1fdad0 00007ff9e38fa001 system.threading.tasks.task.executewiththreadlocal(system.threading.tasks.task byref)
000000da9b1fdb80 00007ff9e38f96e1 system.threading.tasks.task.executeentry(boolean)

!threads 查看编号13的线程类型

0:013> !threads
threadcount:      8
unstartedthread:  0
backgroundthread: 5
pendingthread:    0
deadthread:       2
hosted runtime:   no
                                                                                                        lock  
       id osid threadobj           state gc mode     gc alloc context                  domain           count apt exception
   0    1 5754 000001e2be060f80    2a020 preemptive  000001e2bfd19868:000001e2bfd19fd0 000001e2be053bb0 1     mta 
   6    2 65e0 000001e2be08bd00    2b220 preemptive  0000000000000000:0000000000000000 000001e2be053bb0 0     mta (finalizer) 
   9    3  25c 000001e2d8435ef0  102a220 preemptive  0000000000000000:0000000000000000 000001e2be053bb0 0     mta (threadpool worker) 
xxxx    4    0 000001e2d845ea30  1039820 preemptive  0000000000000000:0000000000000000 000001e2be053bb0 0     ukn (threadpool worker) 
  12    6 23fc 000001e2d8469ea0  202b220 preemptive  000001e2bfd1e188:000001e2bfd1ffd0 000001e2be053bb0 1     mta 
  13    7 62d8 000001e2d8475e20  a029220 preemptive  000001e2bfd9d588:000001e2bfd9f250 000001e2be053bb0 0     mta (threadpool completion port) 
xxxx    8    0 000001e2d847a0b0  8039820 preemptive  0000000000000000:0000000000000000 000001e2be053bb0 0     ukn (threadpool completion port) 
  14    9  6e4 000001e2d847de70  8029220 preemptive  000001e2bfd80d88:000001e2bfd81f10 000001e2be053bb0 0     mta (threadpool completion port) 

其中的 13 7 62d8 000001e2d8475e20 a029220 preemptive 000001e2bfd9d588:000001e2bfd9f250 000001e2be053bb0 0 mta (threadpool completion port) 可以明显的看到是 threadpool completion port,没有骗你吧,

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网