Whatbeg's blog
当你的才华撑不起你的野心时,就应该静下心来好好学习。
2019-06-05T03:41:46.507Z
http://whatbeg.com/
whatbeg
Hexo
进程线程常见基础问题
http://whatbeg.com/2019/06/05/processthread.html
2019-06-05T03:25:24.000Z
2019-06-05T03:41:46.507Z
<p>计算机程序的运行离不开进程和线程,进程和线程也是面试中常常要抠的部分。本文梳理一下进程和线程的一些常见问题,包括其基本概念,区别于联系,高级主题包括线程安全,多线程编程等等。</p>
<p>[TOC]</p>
<h2 id="基本概念"><a href="#基本概念" class="headerlink" title="基本概念"></a>基本概念</h2><p>进程和线程的基本概念,<a href="https://www.cnblogs.com/fuchongjundream/p/3829508.html" target="_blank" rel="external">这篇文章</a>已经梳理的比较好了。不再详述。</p>
<p>简单来说,进程是一个程序(代码,编译过后的二进制文件)在系统中的一次执行过程,包含了地址空间(堆,栈,代码区,数据区,共享库加载区,内核区),各种资源描述符等等。我们常说,进程是资源分配的基本单位。</p>
<p>一个进程包含一个或多个线程,线程拥有进程的所有资源,所有线程共享地址空间,被系统调度去执行。所以常说,线程是调度的基本单位。</p>
<p>进程有三个状态,就绪、运行和阻塞。</p>
<p><img src="https://blog-image-1256228880.cos.ap-beijing.myqcloud.com/processstate.png" alt=""></p>
<p><strong>操作系统中进程调度策略有哪几种?</strong></p>
<p>FIFO,时间片轮转,优先级</p>
<h2 id="进程的创建,销毁,守护进程和僵尸进程"><a href="#进程的创建,销毁,守护进程和僵尸进程" class="headerlink" title="进程的创建,销毁,守护进程和僵尸进程"></a>进程的创建,销毁,守护进程和僵尸进程</h2><p><strong>fork与vfork区别?</strong></p>
<blockquote>
<p>1)fork ():子进程拷贝父进程的数据段,代码段<br>vfork ():子进程与父进程共享数据段<br>2)fork() 父子进程的执行次序不确定<br>vfork() 保证子进程先运行,在调用 exec 或exit 之前与父进程数据是共享的,在它调用exec或exit之后父进程才可能被调度运行。<br>3)vfork() 保证子进程先运行,如果在调用这两个函数之前子进程依赖于父进程的进一步动作,则会导致死锁。<br>由vfork创造出来的子进程还会导致父进程挂起,除非子进程exit或者execve才会唤起父进程<br>由vfok创建出来的子进程共享了父进程的所有内存,包括栈地址,直至子进程使用execve启动新的应用程序为止<br>由vfork创建出来得子进程不应该使用return返回调用者,得使用exit()者_exit()函数来退出</p>
<p>fock创建进程的步骤:简化的copy_process()流程</p>
<p>1)dup_task_struct()。分配一个新的进程控制块,包括新进程在kernel中的堆栈。新的进程控制块会复制父进程的进程控制块,但是因为每个进程都有一个kernel堆栈,新进程的堆栈将被设置成新分配的堆栈。<br>2)初始化一些新进程的统计信息,如此进程的运行时间<br>3)copy_semundo()复制父进程的semaphore undo_list到子进程。<br>4)copy_files()、copy_fs()。复制父进程文件系统相关的环境到子进程<br>5)copy_sighand()、copy_signal()。复制父进程信号处理相关的环境到子进程。<br>6)copy_mm()。复制父进程内存管理相关的环境到子进程,包括页表、地址空间和代码数据。<br>7)copy_thread()/copy_thread_tls。设置子进程的执行环境,如子进程运行时各CPU寄存器的值、子进程的kernel栈的起始地址。<br>8)sched_fork()。设置子进程调度相关的参数,即子进程的运行CPU、初始时间片长度和静态优先级等。<br>9)将子进程加入到全局的进程队列中<br>10)设置子进程的进程组ID和对话期ID等。</p>
<p>内核线程拥有 进程描述符、PID、进程正文段、核心堆栈<br>用户进程拥有 进程描述符、PID、进程正文段、核心堆栈 、用户空间的数据段和堆栈<br>用户线程拥有 进程描述符、PID、进程正文段、核心堆栈,同父进程共享用户空间的数据段和堆栈</p>
</blockquote>
<p><strong>插播:fork(), vfork(), clone()的区别</strong></p>
<blockquote>
<p>fork,vfork,clone都是linux的系统调用,这三个函数分别调用了sys_fork、sys_vfork、sys_clone,最终都调用了do_fork函数,差别在于参数的传递和一些基本的准备工作不同,主要用来linux创建新的子进程或线程(vfork创造出来的是线程)。</p>
<p>进程的四要素:<br>(1)有一段程序供其执行(不一定是一个进程所专有的),就像一场戏必须有自己的剧本。<br>(2)有自己的专用系统堆栈空间(私有财产)<br>(3)有进程控制块(task_struct)(“有身份证,PID”)<br>(4)有独立的存储空间。<br>缺少第四条的称为线程,如果完全没有用户空间称为内核线程,共享用户空间的称为用户线程。</p>
<p><strong>fork()</strong> 创造的子进程复制了父亲进程的资源(写时复制技术),包括内存的内容task_struct内容(2个进程的pid不同)。这里是资源的复制不是指针的复制。<br><strong>vfork()</strong> 是一个过时的应用,vfork也是创建一个子进程,但是子进程共享父进程的空间。在vfork创建子进程之后,父进程阻塞,直到子进程执行了exec()或者exit()。vfork最初是因为fork没有实现COW机制,而很多情况下fork之后会紧接着exec,而exec的执行相当于之前fork复制的空间全部变成了无用功,所以设计了vfork。而现在fork使用了COW机制,唯一的代价仅仅是复制父进程页表的代价,所以vfork不应该出现在新的代码之中。<br>vfork创建出来的不是真正意义上的进程,而是一个线程,因为它缺少经常要素(4),独立的内存资源,<br><strong>clone()</strong> 是Linux为创建线程设计的(虽然也可以用clone创建进程)。所以可以说clone是fork的升级版本,不仅可以创建进程或者线程,还可以指定创建新的命名空间(namespace)、有选择的继承父进程的内存、甚至可以将创建出来的进程变成父进程的兄弟进程等等。<br>clone函数功能强大,带了众多参数,它提供了一个非常灵活自由的常见进程的方法。因此由他创建的进程要比前面2种方法要复杂。clone可以让你有选择性的继承父进程的资源,你可以选择想vfork一样和父进程共享一个虚存空间,从而使创造的是线程,你也可以不和父进程共享,你甚至可以选择创造出来的进程和父进程不再是父子关系,而是兄弟关系。先有必要说下这个函数的结构:<br>int clone(int (<em>fn)(void </em>), void <em>child_stack, int flags, void </em>arg);</p>
<p>clone 和 fork 的区别<br>1)clone和fork的调用方式很不相同,clone调用需要传入一个函数,该函数在子进程中执行。<br>2)clone和fork最大不同在于clone不再复制父进程的栈空间,而是自己创建一个新的。 (void *child_stack,)也就是第二个参数,需要分配栈指针的空间大小,所以它不再是继承或者复制,而是全新的创造。</p>
</blockquote>
<p><strong>exit()与_exit()区别?</strong></p>
<blockquote>
<p>exit在结束进程之前要做以下的事情:</p>
<p>1)调用atexit()注册的函数(出口函数)<br>我们可以使用atexit()函数在main函数结束时对整个进程的内存空间进行销毁,作用相当于C++中的析构函数.<br>2)调用cleanup()关闭所有的流<br>这一步操作导致所有的缓冲被输出<br>3)最后调用_exit()函数终止进程<br>_exit()函数主要做了清理内存空间,结束进程调用等工作。</p>
</blockquote>
<p><strong>僵尸进程和孤儿进程有什么区别、如何处理?</strong></p>
<blockquote>
<p>区别:僵尸进程占用一个进程ID号,占用资源,危害系统。但孤儿进程与僵尸进程不同的是,由于父进程已经死亡,子系统会帮助父进程回收处理孤儿进程。所以孤儿进程实际上是不占用资源的,因为它最终是被系统回收了,不会像僵尸进程那样占用ID,损害运行系统。<br>1)僵尸进程:一个进程使用fork创建子进程,如果子进程退出,而父进程没有调用wait或者waitpid获取子进程的状态,那么子进程的进程描述符仍然保存在系统中,这种进程称为僵尸进程。<br>2)孤儿进程:一个父进程退出,而他的一个或者多个子进程还在运行,那么那些子进程称为孤儿进程。孤儿进程将被init(进程号为1)收养,并由init进程对它们完成状态收集的工作。子进程的死亡需要父进程来处理,所以正常的进程应该是子进程先于父进程死亡,当父进程先于子进程死亡时,子进程死亡没有父进程处理,这个死亡的子进程就是孤儿进程。</p>
<p>简单来说。<br>僵尸进程:父进程没死,子进程死了,但是父进程不帮他收尸(通过wait,waitpid获取其状态),所以变成僵尸。<br>孤儿进程:父进程死了,子进程没死,子进程成了孤儿,只能被孤儿院(init)收养。</p>
</blockquote>
<p><strong>怎样避免僵尸进程呢?</strong></p>
<blockquote>
<p>单独一个线程wait子进程,或者,有两个信号,一个SIGCHLD、一个SIGCLD,设置这两个信号的处理方式为忽略,它们告诉内核,不关心子进程结束的状态所以当子进程终止的时候直接释放所有资源就行。它们的区别是SIGCLD在安装完信号处理函数的时候还会检查是否已经存在结束的子进程,如果有就调用信号处理函数,而SIGCHLD不会,也就是可能会丢掉已经有子进程已经结束这个事实</p>
</blockquote>
<p><strong>如何实现守护进程?</strong></p>
<blockquote>
<p>守护进程(Daemon)是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。守护进程是一种很有用的进程。<br>1、守护进程最重要的特性是后台运行。<br>2、守护进程必须与其运行前的环境隔离开来。这些环境包括未关闭的文件描述符,控制终端,会话和进程组,工作目录以及文件创建掩模等。这些环境通常是守护进程从执行它的父进程(特别是shell)中继承下来的。<br>3、守护进程的启动方式有其特殊之处。它可以在Linux系统启动时从启动脚本/etc/rc.d中启动,可以由作业规划进程crond启动,还可以由用户终端(shell)执行。</p>
<p>守护进程之编程规则<br>(1)首先要做的是调用umask将文件模式创建屏蔽字设置为0。<br>文件权限掩码:是指屏蔽掉文件权限中的对应位。例如,有个文件权限掩码是050,它就屏蔽了文件组拥有者的可读与可执行权限(对应二进制为,rwx, 101)。由于fork函数创建的子进程继承了父进程的文件权限掩码,这就给子进程使用文件带来了诸多的麻烦。因此,把文件权限掩码设置为0(即,不屏蔽任何权限),可以增强该守护进程的灵活性。设置文件权限掩码的函数是umask。通常的使用方法为umask(0)。<br>(2)调用fork,然后使父进程退出(exit)。if(pid=fork()) exit(0);<br>(3)调用setsid以创建一个新会话,脱离控制终端和进程组。setsid函数作用:用于创建一个新的会话,并担任该会话组的组长。<br>调用setsid有3个作用:(a) 让进程摆脱原会话的控制;(b) 让进程摆脱原进程组的控制;(c) 让进程摆脱原控制终端的控制; setsid()<br>使用setsid函数的目的:由于创建守护进程的第一步调用了fork函数来创建子进程再将父进程退出。由于在调用fork函数时,子进程拷贝了父进程的会话期、进程组、控制终端等,虽然父进程退出了,但会话期、进程组、控制终端等并没有改变,因此,这还不是真正意义上的独立开了。使用setsid函数后,能够使进程完全独立出来,从而摆脱其他进程的控制。<br>(4)将当前工作目录更改为根目录。<br>(5)关闭不再需要的文件描述符。这使守护进程不再持有从其父进程继承来的某些文件描述符(父进程可能是shell进程,或某个其他进程)。<br>(6)某些守护进程打开/dev/null使其具有文件描述符0、1和2,</p>
</blockquote>
<p><img src="https://blog-image-1256228880.cos.ap-beijing.myqcloud.com/daemonize.jpg" alt=""></p>
<h2 id="进程内存管理"><a href="#进程内存管理" class="headerlink" title="进程内存管理"></a>进程内存管理</h2><p><strong>进程的内存空间布局?</strong></p>
<blockquote>
<p>进程寻址空间0~4G<br>进程在用户态只能访问0~3G,只有进入内核态才能访问3G~4G<br>进程通过系统调用进入内核态<br>每个进程虚拟空间的3G~4G部分是相同的<br>进程从用户态进入内核态不会引起CR3的改变但会引起堆栈的改变</p>
</blockquote>
<p><img src="https://blog-image-1256228880.cos.ap-beijing.myqcloud.com/processaddress.jpg" alt=""></p>
<p><strong>32位系统一个进程最多有多少堆内存?</strong></p>
<blockquote>
<p>理论上是4G. Linux实现的是 虚拟地址的前3G供给用户态的进程. 后1G是内核的部分. 也就是用户态的进程不能访问0xc0000000以上的虚拟地址.</p>
</blockquote>
<p><strong>进程空间和内核空间对内存的管理不同</strong></p>
<blockquote>
<p>进程内存管理的对象是进程线性地址空间上的内存镜像(虚拟内存),这些内存镜像其实就是进程使用的虚拟内存区域(memory region)。进程虚拟空间是个32或64位的“平坦”(独立的连续区间)地址空间(空间的具体大小取决于体系结构)。要统一管理这么大的平坦空间可绝非易事,为了方便管理,虚拟空间被划分为许多大小可变的(但必须是4096的倍数)内存区域,这些区域在进程线性地址中像停车位一样有序排列。这些区域的划分原则是“将访问属性一致的地址空间存放在一起”,所谓访问属性在这里无非指的是“可读、可写、可执行等”。<br>Linux内核管理物理内存是通过分页机制实现的,它将整个内存划分成无数个4k(在i386体系结构中)大小的页,从而分配和回收内存的基本单位便是内存页了。利用分页管理有助于灵活分配内存地址,因为分配时不必要求必须有大块的连续内存,系统可以东一页、西一页的凑出所需要的内存供进程使用。虽然如此,但是实际上系统使用内存时还是倾向于分配连续的内存块,因为分配连续内存时,页表不需要更改,因此能降低TLB的刷新率(频繁刷新会在很大程度上降低访问速度)。</p>
</blockquote>
<p><strong>虚拟内存的作用?</strong></p>
<blockquote>
<p>1)内存访问保护<br>我们就可以通过设置段界限或页表项来设定软件运行时的访问空间,确保软件运行不越界,完成内存访问保护的功能。<br>2)按需分页(lazy load 技术)<br>通过内存地址虚拟化,可以使得软件在没有访问某虚拟内存地址时不分配具体的物理内存,而只有在实际访问某虚拟内存地址时,操作系统再动态地分配物理内存,建立虚拟内存到物理内存的页映射关系<br>3)页换入换出(page swap in/out)<br>把不经常访问的数据所占的内存空间临时写到硬盘上,这样可以腾出更多的空闲内存空间给经常访问的数据;当CPU访问到不经常访问的数据时,再把这些数据从硬盘读入到内存中<br>4)写时复制(copy on write)<br>两个虚拟页的数据内容相同时,可只分配一个物理页框,这样如果对两个虚拟页的访问方式是只读方式,这这两个虚拟页可共享页框,节省内存空间;如果CPU对其中之一的虚拟页进行写操作,则这两个虚拟页的数据内容会不同,需要分配一个新的物理页框,并将物理页框标记为可写,这样两个虚拟页面将映射到不同的物理页帧,确保整个内存空间的正确访问。</p>
</blockquote>
<p><strong>虚拟内存的实现?</strong></p>
<blockquote>
<p>虚拟内存中,允许将一个作业分多次调入内存。釆用连续分配方式时,会使相当一部分内存空间都处于暂时或“永久”的空闲状态,造成内存资源的严重浪费,而且也无法从逻辑上扩大内存容量。因此,虚拟内存的实现需要建立在离散分配的内存管理方式的基础上。虚拟内存的实现有以下三种方式:<br>请求分页存储管理。<br>请求分段存储管理。<br>请求段页式存储管理。<br>不管哪种方式,都需要有一定的硬件支持。一般需要的支持有以下几个方面:<br>一定容量的内存和外存。<br>页表机制(或段表机制),作为主要的数据结构。<br>中断机构,当用户程序要访问的部分尚未调入内存,则产生中断。<br>地址变换机构,逻辑地址到物理地址的变换。</p>
</blockquote>
<p><strong>Linux是如何避免内存碎片的?内存分配原理?</strong></p>
<blockquote>
<p>频繁地请求和释放不同大小的内存,必然导致内存碎片问题的产生,结果就是当再次要求分配连续的内存时,即使整体内存是足够的,也无法满足连续内存的需求。该问题也称之为外碎片(external fragmentation)。<br>解决方案:<br>避免外碎片的方法有两种:<br>1)利用分页单元把一组非连续的空闲页框映射到连续的线性地址<br>2)开发一种适当的技术来记录现存的空闲的连续页框块的情况,以尽量避免为满足对小块的请求而分割大的空闲快<br>第一种方案的意思是,我们使用地址转换技术,把非连续的物理地址转换成连续的线性地址。<br>第二种方案的意思是,开发一种特有的分配技术来记录下来空闲内存的情况,从而解决内存碎片问题。<br>Linux采用了第二种方案,因为在某些情况下,系统的确需要连续的物理地址(DMA处理器可以直接访问总线)。</p>
</blockquote>
<p><strong>伙伴系统?</strong></p>
<blockquote>
<p>我们通过一个例子来说明伙伴算法的工作原理,假设现在要请求一个256个页框的块(1MB),算法步骤如下:<br>• 在256个页框的链表中检查是否有一个空闲快,如果没有,查找下一个更大的块,如果有,请求满足。<br>• 在512个页框的链表中检查是否有一个空闲块,如果有,把512个页框的空闲块分为两份,第一份用于满足请求,第二份链接到256个页框的链表中。如果没有空闲块,继续寻找下一个更大的块。<br>页的请求<br>以上过程的逆过程,就是页框块的释放过程,也是该算法名字的由来,内核试图把大小为B的一对空闲伙伴块合并为一个2B的单独块,满足以下条件的两个块称之为伙伴:<br>• 两个块具有相同的大小<br>• 他们的物理地址是连续的<br>第一块的第一个页框的物理地址是2 <em> B </em> 2^12<br>该算法是递归的,如果它成功合并了B,就会试图去合并2B,以再次试图形成更大的块。</p>
</blockquote>
<p><strong>高速缓存Slab层?分类管理对象</strong></p>
<blockquote>
<p>slab是Linux操作系统的一种内存分配机制。其工作是针对一些经常分配并释放的对象,如进程描述符等,这些对象的大小一般比较小,如果直接采用伙伴系统来进行分配和释放,不仅会造成大量的内存碎片,而且处理速度也太慢。<br>而slab分配器是基于对象进行管理的,<strong>相同类型的对象归为一类</strong> (如进程描述符就是一类),每当要申请这样一个对象,slab分配器就从一个slab列表中分配一个这样大小的单元出去,而当要释放时,将其重新保存在该列表中,而不是直接返回给伙伴系统,从而避免这些内碎片。slab分配器并不丢弃已分配的对象,而是释放并把它们保存在内存中。当以后又要请求新的对象时,就可以从内存直接获取而不用重复初始化。</p>
</blockquote>
<p><strong>共享内存的实现原理?</strong></p>
<blockquote>
<p>共享内存可以说是最有用的进程间通信方式,也是最快的IPC形式。两个不同进程A、B共享内存的意思是,同一块物理内存被映射到进程A、B各自的进程地址空间。进程A可以即时看到进程B对共享内存中数据的更新,反之亦然。由于多个进程共享同一块内存区域,必然需要某种同步机制,互斥锁和信号量都可以。</p>
<p>采用共享内存通信的一个显而易见的好处是效率高,因为进程可以直接读写内存,而不需要任何数据的拷贝。对于像管道和消息队列等通信方式,则需要在内核和用户空间进行四次的数据拷贝,而共享内存则只拷贝两次数据[1]:一次从输入文件到共享内存区,另一次从共享内存区到输出文件。实际上,进程之间在共享内存时,并不总是读写少量数据后就解除映射,有新的通信时,再重新建立共享内存区域。而是保持共享区域,直到通信完毕为止,这样,数据内容一直保存在共享内存中,并没有写回文件。共享内存中的内容往往是在解除映射时才写回文件的。因此,采用共享内存的通信方式效率是非常高的。</p>
<p>Linux的2.2.x内核支持多种共享内存方式,如mmap()系统调用,Posix共享内存,以及系统V共享内存。linux发行版本如Redhat 8.0支持mmap()系统调用及系统V共享内存,但还没实现Posix共享内存,本文将主要介绍mmap()系统调用及系统V共享内存API的原理及应用。</p>
</blockquote>
<h2 id="进程间通信-IPC"><a href="#进程间通信-IPC" class="headerlink" title="进程间通信(IPC)"></a>进程间通信(IPC)</h2><p><strong>进程间通信(IPC)方式?</strong></p>
<blockquote>
<p>IPC: 管道、FIFO、信号、信号量、消息队列、共享内存、套接字</p>
<ol>
<li>管道:速度慢,容量有限,无名管道只有父子进程能通讯<br>有名管道 (named pipe) : 有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。<br>2 消息队列通信<br>消息队列( message queue ) : 消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。<br>3 信号量通信<br>信号量( semophore ) : 信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为<strong>进程间</strong>以及<strong>同一进程内不同线程之间</strong>的同步手段。<br>4 信号<br>信号 ( sinal ) : 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。<br>5 共享内存通信<br>共享内存( shared memory ) :共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量,配合使用,来实现进程间的同步和通信。<br>6 套接字通信<br>套接字( socket ) : 套接口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同机器间的进程通信.</li>
</ol>
</blockquote>
<h2 id="进程、线程同步"><a href="#进程、线程同步" class="headerlink" title="进程、线程同步"></a>进程、线程同步</h2><blockquote>
<p>线程同步的方式主要有: 临界区(Critical Section)、互斥量(Mutex)、信号量(Semaphore)、事件(Event)。<br>他们的主要区别和特点如下:<br>1)临界区:通过对多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问。在任意时刻只允许一个线程对共享资源进行访问,<br>如果有多个线程试图访问公共资源,那么在有一个线程进入后,其他试图访问公共资源的线程将被挂起,并一直等到进入临界区的线程离开,临界区在被释放后,其他线程才可以抢占。<br>2)互斥量:采用互斥对象机制。 只有拥有互斥对象的线程才有访问公共资源的权限,因为互斥对象只有一个,所以能保证公共资源不会同时被多个线程访问。<br>互斥不仅能实现同一应用程序的公共资源安全共享,还能实现不同应用程序的公共资源安全共享。<br>3)信号量:它允许多个线程在同一时刻访问同一资源,但是需要限制在同一时刻访问此资源的最大线程数目。<br>4)事 件: 通过通知操作的方式来保持线程的同步,还可以方便实现对多个线程的优先级比较的操作。</p>
</blockquote>
<p><img src="https://img-blog.csdn.net/20171121130628916" alt=""></p>
<h2 id="内核进程和用户进程"><a href="#内核进程和用户进程" class="headerlink" title="内核进程和用户进程"></a>内核进程和用户进程</h2><p><strong>内核级调度和用户级调度?</strong></p>
<blockquote>
<p>内核线程:由操作系统内核创建和撤销,内核空间实现还为每个内核支持线程设置了一个线程控制块,内核是根据该控制块而感知某个线程是否存在,并加以控制。线程切换也由内核控制,切换的时候,要从用户态进入内核态,切换完毕要从内核态返回用户态。可以很好的利用多CPU<br>用户级线程:仅存在于用户空间中。线程的创建、撤销、线程之间的同步和通信等功能,都无需系统调用来实现。对于同一进程的线程之间切换仍然是不需要内核支持的。因此,内核也不知道用户级线程的存在,少了进出内核态的消耗,但不能很好的利用多CPU。</p>
<p>1)用户线程:库调度器从进程的多个线程中选择一个线程,然后该线程和该进程允许的一个内核线程关联起来。内核线程将被操作系统调度器指派到处理器内核。用户级线程是一种”多对一”的线程映射。<br>2)内核级线程:内核线程建立和销毁都是由操作系统负责、通过系统调用完成的。在内核的支持下运行,无论是用户进程的线程,或者是系统进程的线程,他们的创建、撤销、切换都是依靠内核实现的。线程管理的所有工作由内核完成,应用程序没有进行线程管理的代码,只有一个到内核级线程的编程接口。内核为进程及其内部的每个线程维护上下文信息,调度也是在内核基于线程架构的基础上完成。<br>内核线程驻留在内核空间,它们是内核对象。有了内核线程,每个用户线程被映射或绑定到一个内核线程。用户线程在其生命期内都会绑定到该内核线程。一旦用户线程终止,两个线程都将离开系统。这被称作一对一线程映射,线程的创建、撤销和切换等,都需要内核直接实现,即内核了解每一个作为可调度实体的线程。<br>这些线程可以在全系统内进行资源的竞争内核空间内为每一个内核支持线程设置了一个线程控制块(TCB),内核根据该控制块,感知线程的存在,并进行控制。<br>如图所示,即内核级线程的实现方式,每个用户线程都直接与一个内核线程相关联。操作系统调度器管理、调度并分派这些线程。运行时库为每个用户级线程请求一个内核级线程。操作系统的内存管理和调度子系统必须要考虑到数量巨大的用户级线程。您必须了解每个进程允许的线程的最大数目是多少。操作系统为每个线程创建上下文。进程的每个线程在资源可用时都可以被指派到处理器内核。</p>
<p>内核线程的优点:<br>1)多处理器系统中,内核能够并行执行同一进程内的多个线程。<br>2)如果进程中的一个线程被阻塞,能够切换同一进程内的其他线程继续执行(用户级线程的一个缺点)。<br>3)所有能够阻塞线程的调用都以系统调用的形式实现,代价可观。<br>4)当一个线程阻塞时,内核根据选择可以运行另一个进程的线程,而用户空间实现的线程中,运行时系统始终运行自己进程中的线程。<br>5)信号是发给进程而不是线程的,当一个信号到达时,应该由哪一个线程处理它?线程可以“注册”它们感兴趣的信号。</p>
<p>组合型:在一些系统中,使用组合方式的多线程实现,线程创建完全在用户空间中完成,线程的调度和同步也在应用程序中进行。一个应用程序中的多个用户级线程被映射到一些(小于或等于用户级线程的数目)内核级线程上。下图说明了用户级与内核级的组合实现方式,在这种模型中,每个内核级线程有一个可以轮流使用的用户级线程集合。</p>
</blockquote>
<p><strong>Linux用户级进程跟内核线程(进程)有什么差别?</strong></p>
<blockquote>
<p>区别:<br>1)内核级线程是OS内核可感知的,而用户级线程是OS内核不可感知的。<br>2)用户级线程的创建、撤消和调度不需要OS内核的支持,是在语言(如Java)这一级处理的;而内核支持线程的创建、撤消和调度都需OS内核提供支持,而且与进程的创建、撤消和调度大体是相同的。<br>3)用户级线程执行系统调用指令时将导致其所属进程被中断,而内核支持线程执行系统调用指令时,只导致该线程被中断。<br>4)在只有用户级线程的系统内,CPU调度还是以进程为单位,处于运行状态的进程中的多个线程,由用户程序控制线程的轮换运行;在有内核支持线程的系统内,CPU调度则以线程为单位,由OS的线程调度程序负责线程的调度。<br>5)用户级线程的程序实体是运行在用户态下的程序,而内核支持线程的程序实体则是可以运行在任何状态下的程序。</p>
</blockquote>
<p><strong>为什么要区分用户态和内核态?</strong></p>
<blockquote>
<p>在CPU的所有指令中,有些指令是非常危险的,如果错用,将导致系统崩溃,比如清内存、设置时钟等。<br>如果所有的程序都能使用这些指令,那么系统死机的概率将大大增加。<br>所以,CPU将指令分为特权指令和非特权指令,对于那些危险的指令,只允许操作系统及其相关模块使用,普通应用程序只能使用那些不会造成灾难的指令。<br>Intel的CPU将特权等级分为4个级别:Ring0~Ring3<br>Linux使用Ring3级别运行用户态,Ring0作为内核态。<br>Linux的内核是一个有机整体,每个用户进程运行时都好像有一份内核的拷贝。每当用户进程使用系统调用时,都自动地将运行模式从用户级转为内核级(成为陷入内核),此时,进程在内核的地址空间中运行。</p>
</blockquote>
<p><strong>从用户空间到内核空间有以下触发手段?</strong></p>
<blockquote>
<p>1)系统调用:用户进程通过系统调用申请使用操作系统提供的服务程序来完成工作,比如read()、fork()等。系统调用的机制其核心还是使用了操作系统为用户特别开放的一个中断来实现的。<br>2)中断:当外围设备完成用户请求的操作后,会向CPU发送中断信号。这时CPU会暂停执行下一条指令(用户态)转而执行与该中断信号对应的中断处理程序(内核态)<br>3)异常:当CPU在执行运行在用户态下的程序时,发生了某些事先不可知的异常,这时会触发由当前运行进程切换到处理此异常的内核相关程序中,也就转到了内核态,比如缺页异常。</p>
</blockquote>
<h2 id="线程"><a href="#线程" class="headerlink" title="线程"></a>线程</h2><p><strong>Linux线程是如何进行切换的?</strong></p>
<blockquote>
<p>(1)一般的进程切换分为两步 :<br>1)切换页目录使用新的地址空间<br>2)切换内核栈和硬件上下文<br>对于Linux来讲,地址空间是线程和进程的最大区别,如果是线程切换的话,不需要做第一步,也就是切换页目录使用新的地址空间。但是切换内核栈和硬件上下文则是线程切换和进程切换都需要做的。<br>(2)切换进程上下文:<br>进程上下文可以分为三个部分:<br>用户级上下文: 正文、数据、用户堆栈以及共享存储区;<br>寄存器上下文: 通用寄存器、程序寄存器(IP)、处理器状态寄存器(EFLAGS)、栈指针(ESP);<br>系统级上下文: 进程控制块task_struct、内存管理信息(mm_struct、vm_area_struct、pgd、pte)、内核栈。<br> 系统中的每一个进程都有自己的上下文。一个正在使用处理器运行的进程称为当前进程(current)。当前进程因时间片用完或者因等待某个事件而阻塞时,进程调度需要把处理器的使用权从当前进程交给另一个进程,这个过程叫做进程切换。此时,被调用进程成为当前进程。在进程切换时系统要把当前进程的上下文保存在指定的内存区域(该进程的任务状态段TSS中),然后把下一个使用处理器运行的进程的上下文设置成当前进程的上下文。当一个进程经过调度再次使用CPU运行时,系统要恢复该进程保存的上下文。所以,进程的切换也就是上下文切换。<br>(3)线程切换:<br> Linux下的线程实质上是轻量级进程(light weighted process),线程生成时会生成对应的进程控制结构,只是该结构与父线程的进程控制结构共享了同一个进程内存空间。同时新线程的进程控制结构将从父线程(进程)处复制得到同样的进程信息,如打开文件列表和信号阻塞掩码等。创建线程比创建新进程成本低,因为新创建的线程使用的是当前进程的地址空间。相对于在进程之间切换,在线程之间进行切换所需的时间更少,因为后者不包括地址空间之间的切换。<br>线程切换上下文切换的原理与此类似,只是线程在同一地址空间中,不需要MMU等切换,只需要切换必要的CPU寄存器,因此,线程切换比进程切换快的多。</p>
</blockquote>
<p><strong>异步,多线程和并行的区别?</strong></p>
<blockquote>
<p>(1)并发:在操作系统中,是指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机上运行。其中两种并发关系分别是同步和互斥<br>其中并发又有伪并发和真并发,伪并发是指单核处理器的并发,真并发是指多核处理器的并发。<br>(2)并行:在单处理器中多道程序设计系统中,进程被交替执行,表现出一种并发的外部特种;在多处理器系统中,进程不仅可以交替执行,而且可以重叠执行。在多处理器上的程序才可实现并行处理。从而可知,并行是针对多处理器而言的。并行是同时发生的多个并发事件,具有并发的含义,但并发不一定并行,也亦是说并发事件之间不一定要同一时刻发生。<br>(3)互斥:进程间相互排斥的使用临界资源的现象,就叫互斥。<br>(4)同步:进程之间的关系不是相互排斥临界资源的关系,而是相互依赖的关系。进一步的说明:就是前一个进程的输出作为后一个进程的输入,当第一个进程没有输出时第二个进程必须等待。具有同步关系的一组并发进程相互发送的信息称为消息或事件。<br>(5)异步:异步和同步是相对的,同步就是顺序执行,执行完一个再执行下一个,需要等待、协调运行。异步就是彼此独立,在等待某事件的过程中继续做自己的事,不需要等待这一事件完成后再工作。线程就是实现异步的一个方式。异步是让调用方法的主线程不需要同步等待另一线程的完成,从而可以让主线程干其它的事情。<br>(6)多线程:多线程是程序设计的逻辑层概念,它是进程中并发运行的一段代码。多线程可以实现线程间的切换执行。</p>
</blockquote>
<p><strong>ThreadLocal?</strong></p>
<blockquote>
<p>ThreadLocal 是线程的局部变量, 是每一个线程所单独持有的。<br>我们知道有时候一个对象的变量会被多个线程所访问,这时就会有线程安全问题,当然我们可以使用synchorinized 关键字来为此变量加锁,进行同步处理,从而限制只能有一个线程来使用此变量,但是加锁会大大影响程序执行效率,此外我们还可以使用ThreadLocal来解决对某一个变量的访问冲突问题。<br>当使用ThreadLocal维护变量的时候 为每一个使用该变量的线程提供一个独立的变量副本,即每个线程内部都会有一个该变量,这样同时多个线程访问该变量并不会彼此相互影响,因此他们使用的都是自己从内存中拷贝过来的变量的副本, 这样就不存在线程安全问题,也不会影响程序的执行性能。<br>但是要注意,虽然ThreadLocal能够解决上面说的问题,但是由于在每个线程中都创建了副本,所以要考虑它对资源的消耗,比如内存的占用会比不使用ThreadLocal要大。</p>
<p>总结:<br>1/ 每个ThreadLocal只能保存一个变量副本,如果想要上线一个线程能够保存多个副本以上,就需要创建多个ThreadLocal。<br>2/ ThreadLocal内部的ThreadLocalMap键为弱引用,会有内存泄漏的风险。<br>3/ 适用于无状态,副本变量独立后不影响业务逻辑的高并发场景。如果如果业务逻辑强依赖于副本变量,则不适合用ThreadLocal解决,需要另寻解决方案。比如:每个线程访问数据库都应当是一个独立的Session会话,如果多个线程共享同一个Session会话,有可能其他线程关闭连接了,当前线程再执行提交时就会出现会话已关闭的异常,导致系统异常。此方式能避免线程争抢Session,提高并发下的安全性。<br>使用ThreadLocal的典型场景正如上面的数据库连接管理,线程会话管理等场景,只适用于独立变量副本的情况,如果变量为全局共享的,则不适用在高并发下使用。</p>
</blockquote>
<h2 id="进程线程区别"><a href="#进程线程区别" class="headerlink" title="进程线程区别"></a>进程线程区别</h2><p>进程和线程的主要差别在于它们是不同的操作系统资源管理方式。<br>1)进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响.<br>而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮.<br>2)在进程切换时,耗费资源较大,效率要差一些。</p>
<p><strong>Linux系统中 进程 、线程 、时间片的关系?</strong></p>
<blockquote>
<p>在Linux系统中,对于用户创建的进程(线程)来说,CPU分配时间片的单位是线程,线程是实际工作的单元,进程只是一个容器,用来管理一个或多个线程。<br>所以在理论上应该尽量使用多线程并发,这样可以抢到更多的时间片,但是实际问题是当线程数过多时,操作系统必须进行调度,也就是分配时间片。线程被调度的时候需要进行上下文的切换,这种操作是一种额外的开销。线程数量过多时,上下文切换会产生额外的开销,对系统效率造成负面的影响。<br>线程和进程有优先级,在抢占式的调度中,优先级高的线程可以从优先级低的线程那里抢占CPU。另外,在多CPU平台上,调度算法还要考虑缓存的关联性。</p>
</blockquote>
<p><strong>在Linux系统中,对于用户创建的进程(线程)来说,CPU分配时间片的单位是线程还是进程?</strong></p>
<blockquote>
<p>是线程。线程是实际工作的单元,进程只是一个容器,用来管理一个或多个线程。<br>拓展1:这是不是就意味着尽量使用多线程并发,这样可以抢到更多的时间片。<br>理论上是的,多线程的一种用途就是能同时做好几件事情,以提高效率。但实际问题是,CPU的数量(核心数)是有限的,而且并不多。如果你的CPU有8个CPU,并且整个系统中有8个线程的话,不考虑中断等因素,每个线程理论上能一直执行下去。然而多于8个线程以后,操作系统就必须进行调度,也就是分配时间片。具体的调度算法有很多种。如果一个进程创建了很多线程的话,最多也只有8个能够处于执行的状态,其余的线程必须等待调度。线程被调度的时候需要进行上下文切换,这个操作是一种额外的开销。线程数量过多的时候,上下文切换产生的额外开销会对系统的效率造成负面影响。<br>拓展2:操作系统对于拥有多线程的进程,是否会减少其每个线程的时间片,或做其他处理来保证公平性?<br>这就是调度算法需要考虑和优化的问题。比如线程和进程有优先级,在抢占式的调度中,优先级高的线程可以从优先级低的线程那里抢占CPU,同样优先级的线程才会轮转CPU。另外,在多CPU平台上,调度算法还要考虑缓存的关联性等。在一个进程中的多个线程要注意在可能的情况下将本线程阻塞,将剩余的时间片让给兄弟线程。<br>在主流的操作系统实现里,一般进程是不能直接控制自己的线程的执行顺序的。也就是说,把一个线程挂起并不能保证另一个线程一定能够被执行。<br>Ps:Linux内核其实不区分进程和线程,内核把执行单元叫做任务(task)。操作系统实际上调度的是进程,进程通过fork()来创建同样的另一个进程。每个进程有一个PID,同一组进程中最先启动的那个进程还有一个TGID。严格来说前者应该叫线程ID,后者应该叫进程ID。Linux里的线程实际上是共享一些资源的一系列进程而已。</p>
</blockquote>
<p><strong>Linux 下多线程和多进程程序的优缺点,各自适合什么样的业务场景?</strong></p>
<blockquote>
<p>(1)多线程优点:<br>1)多线程更快捷:每个线程共享一个虚拟地址空间,因此从调度开销方面考虑多线程占优。<br>2)数据共享方便:由于同一进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其它线程所用,快捷而且方便;<br>3)使多CPU系统更加有效。操作系统会保证当线程数不大于CPU数目时,不同的线程运行于不同的CPU上;<br>4)调度开销更小,不用切换地址空间。<br>(2)多线程缺点:<br>1)稳定性差:一个线程挂掉整个程序挂。<br>2)数据脏:线程间由于共享内存空间而导致数据脏,需要加锁实现数据同步,导致性能低下。<br>3)线程数量收到限制:每个线程与主程序共用地址空间,受限于4GB地址空间,此时再增加CPU也不能提高性能。</p>
<p>(3)多进程优点:<br>1)多进程更稳定:一个进程挂掉不会影响其它进程。<br>2)数据干净:进程之间的内存空间相对独立,各个进程之间的变量不会相互影响。<br>3)更好的多核可伸缩性:每个子进程都有4GB地址空间和相关资源,因此通过增加CPU,就可以容易扩充性能;<br>(4)多进程缺点:<br>1)开销更大:每个进程都维护专属的虚拟地址空间,需要额外的开销<br>2)数据共享困难:各进程间的数据相对独立,不易共享。<br>3)调度开销大:每次调度都需要切换地址空间,切换进程上下文</p>
<p>(5)适用场景:<br>1)不同任务间需要大量共享数据或频繁通信时,采用多线程。<br>2)需要提供非均质的服务(有优先级任务处理)事件响应有优先级,采用多线程<br>3)与人有IO交互的应用,良好的用户体验(键盘鼠标的输入,立刻响应)<br>4)如果工作集较大,就用多线程,避免cpu cache频繁的换入换出;比如memcached缓存系统;<br>5)需要频繁创建销毁的优先用线程<br>6)需要进行大量计算的优先使用线程<br>7)强相关的处理用线程,弱相关的处理用进程<br>8)可能要扩展到多机分布的用进程,多核分布的用线程<br>注:RCU的发明者,Paul McKenny说过:能用多进程方便的解决问题的时候不要使用多线程。</p>
</blockquote>
<h2 id="线程高级特性"><a href="#线程高级特性" class="headerlink" title="线程高级特性"></a>线程高级特性</h2><p><strong>Linux有内核级线程么?</strong></p>
<blockquote>
<p>1)内核线程,只是一个称呼,实际上就是一个进程,有自己独立的TCB,参与内核调度,也参与内核抢占。这个进程的特别之处有两点,第一、该进程没有前台。第二、永远在内核态中运行<br>2)创建内核线程有两种方法,一种是 kthread_create() ,一种是 kernel_thread() </p>
</blockquote>
<p><strong>可重入函数与线程安全的区别与联系?</strong></p>
<blockquote>
<p>1)线程安全是在多个线程情况下引发的,而可重入函数可以在只有一个线程的情况下来说;<br>2)线程安全不一定是可重入的,而可重入函数则一定是线程安全的;<br>3)如果一个函数中有全局变量,那么这个函数既不是线程安全也不是可重入的;<br>4)如果将对临界资源的访问加上锁,则这个函数线程安全的;但如果这个重入函数若加锁未释放则会产生死锁,因此是不可重入的;<br>5)线程安全函数能够是不同的线程访问同一块地址空间,而可重入函数要求不同的执行流对数据的操作互不影响,使结果是相同的;<br>6)如果一个函数当中全是自身占栈空间的,那么既是线程安全又是可重入的</p>
</blockquote>
<p><strong>内存屏障详解?</strong></p>
<blockquote>
<p>内存屏障,在x86 上是”sfence”指令,强迫所有的、在屏障指令之前的 存储指令在屏障以前发生,并且让 store buffers 刷新到发布这个指令的 CPU cache。这将使程序状态对其他 CPU 可见,这样,如果需要它们可以对它做出响应。</p>
</blockquote>
<p><strong>原子操作原理?</strong></p>
<blockquote>
<p>32位IA-32处理器使用基于对缓存加锁或总线加锁的方式来实现多处理器之间的原子操作。首先处理器会自动保证基本的内存操作的原子性。处理器保证从系统内存中读取或者写入一个字节是原子的,意思是当一个处理器读取一个字节时,其他处理器不能访问这个字节的内存地址。Pentium 6和最新的处理器能自动保证单处理器对同一个缓存行里进行16/32/64位的操作是原子的,但是复杂的内存操作处理器是不能自动保证其原子性的,比如跨总线宽度、跨多个缓存行和跨页表的访问。但是,处理器提供总线锁定和缓存锁定两个机制来保证复杂内存操作的原子性。</p>
</blockquote>
<p><strong>malloc和free是线程安全的吗,在多线程开发时用这两个函数应该注意什么?</strong></p>
<blockquote>
<p>是线程安全的,但是不可重入的。<br>如果并发量高,分配频繁,可以考虑使用tcmalloc。tcmalloc是用于优化C++写的多线程应用。可以使得程序在高并发下内存占用更加稳定。<br>在多线程高并发时候最好使用线程池,或者是直接一次性分配好内存,后面复用。<br>malloc/free会导致系统用户态/核心态切换,消耗大。<br>malloc/free线程安全意味着它们要加锁,可以看到任务管理器的锯齿形状<br>不断的malloc/free运行久了会有内存碎片。</p>
<p>malloc函数线程安全但是不可重入的,因为malloc函数在用户空间要自己管理各进程共享的内存链表,由于有共享资源访问,本身会造成线程不安全。为了做到线程安全,需要加锁进行保护。同时这个锁必须是递归锁,因为如果当程序调用malloc函数时收到信号,在信号处理函数里再调用malloc函数,如果使用一般的锁就会造成死锁(信号处理函数中断了原程序的执行),所以要使用递归锁。</p>
<p>虽然使用递归锁能够保证malloc函数的线程安全性,但是不能保证它的可重入性。按上面的场景,程序调用malloc函数时收到信号,在信号处理函数里再调用malloc函数就可能破坏共享的内存链表等资源,因而是不可重入的。</p>
<p>至于malloc函数访问内核的共享数据结构可以正常的加锁保护,因为一个进程程调用malloc函数进入内核时,必须等到返回用户空间前夕才能执行信号处理函数,这时内核数据结构已经访问完成,内核锁已释放,所以不会有问题。</p>
<p>概念<br>1)线程安全:在拥有共享数据的多条线程并行执行的程序中,线程安全的代码会通过同步机制保证各个线程都可以正常并且正确的执行,不会出现数据污染等情况。如果每次运行的结果和单线程运行的结果是一样的,而且其他变量的值和预期也是一样的,就是线程安全的。<br>2)可重入函数:可重入函数可以有多于一个任务并发使用,而不必担心数据错误。不可充数函数不能超过一个数据共享,除非能确保函数的互斥,或者使用信号量,或者在代码的关键部分禁用中断。可重入函数可以在任何时候被中断,稍后继续运行,不会丢失数据。可重入函数要么使用本地,要么使用全局变量保护数据。</p>
</blockquote>
<p><strong>如何理解互斥锁,条件锁,读写锁以及自旋锁?线程中锁有哪几种。互斥锁和自旋锁底层实现机制讲一下,分别运用在什么场合,有什么优缺点?</strong></p>
<blockquote>
<p>读写锁特点:<br>1)多个读者可以同时进行读<br>2)写者必须互斥(只允许一个写者写,也不能读者写者同时进行)<br>3)写者优先于读者(一旦有写者,则后续读者必须等待,唤醒时优先考虑写者)</p>
<p>互斥锁特点:<br>一次只能一个线程拥有互斥锁,其他线程只有等待<br>互斥锁是在抢锁失败的情况下主动放弃CPU进入睡眠状态直到锁的状态改变时再唤醒,而操作系统负责线程调度,为了实现锁的状态发生改变时唤醒阻塞的线程或者进程,需要把锁交给操作系统管理,所以互斥锁在加锁操作时涉及上下文的切换。互斥锁实际的效率还是可以让人接受的,加锁的时间大概100ns左右,而实际上互斥锁的一种可能的实现是先自旋一段时间,当自旋的时间超过阀值之后再将线程投入睡眠中,因此在并发运算中使用互斥锁(每次占用锁的时间很短)的效果可能不亚于使用自旋锁。</p>
<p>条件变量的特点:</p>
<p>互斥锁一个明显的缺点是他只有两种状态:锁定和非锁定。而条件变量通过允许线程阻塞和等待另一个线程发送信号的方法弥补了互斥锁的不足,他常和互斥锁一起使用,以免出现竞态条件。当条件不满足时,线程往往解开相应的互斥锁并阻塞线程然后等待条件发生变化。一旦其他的某个线程改变了条件变量,他将通知相应的条件变量唤醒一个或多个正被此条件变量阻塞的线程。总的来说互斥锁是线程间互斥的机制,条件变量则是同步机制。</p>
<p>自旋锁的特点:</p>
<p>如果进线程无法取得锁,进线程不会立刻放弃CPU时间片,而是一直循环尝试获取锁,直到获取为止。如果别的线程长时期占有锁那么自旋就是在浪费CPU做无用功,但是自旋锁一般应用于加锁时间很短的场景,这个时候效率比较高。</p>
</blockquote>
<p><strong>互斥锁,同步锁,临界区,互斥量,信号量,自旋锁之间联系是什么?</strong></p>
<blockquote>
<p>1、临界区:通过对多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问。<br>2、互斥量:为协调共同对一个共享资源的单独访问而设计的,互斥对象只有一个。<br>3、信号量:为控制一个具有有限数量用户资源而设计,只能在进程上下文中使用,适合长时间访问共享资源的情况<br>4、自旋锁:适合短时间访问共享资源的情况,如果锁被长时间持有,等待线程会消耗大量资源<br>5、事件:用来通知线程有一些事件已发生,从而启动后继任务的开始。</p>
</blockquote>
<p><strong>pthread_cond_wait 为什么需要传递 mutex 参数?</strong></p>
<blockquote>
<p>pthread_cond_wait 函数用于等待目标条件变量。mutex参数用于保护条件变量的互斥锁,以确保pthread_cond_wait 操作的原子性,在调用pthread_cond_wait前,必须确保互斥锁mutex已经加锁,否则将导致不可预期的结果。pthread_cond_wait函数执行时,首先把调用线程放入条件变量的等待队列中,然后将互斥锁mutex解锁。可见,从ptread_cond_wait开始执行到其调用线程被放入条件变量的等待队列之间的这段时间内,ptread_cond_signal 和pthread_cond_broadcast 函数不会修改条件变量,换言之,pthread_cond_wait 函数不会错过目标条件变量的任何变化,当pthread_cond_wait 函数成功返回时,互斥锁mutex将被再次锁上。</p>
</blockquote>
<p><strong>死锁的原因和避免?</strong></p>
<blockquote>
<p>一、死锁的概念<br>所谓死锁,是指多个进程在运行过程中因争夺资源而照成的一种僵局。当进程处于这种僵持状态时,若无外力作用,它们都将无法再向前推进。<br>二、产生死锁的原因<br>(1)竞争资源。当系统中供多个进程共享的资源如打印机、公用队列等,其数目不足以满足诸进程的需要时,会引起诸进程对资源的竞争而产生死锁。<br>(2)进程间推进顺序非法。进程在运行过程中,请求和释放资源的顺序不当,也同样会产生进程死锁。<br>三、产生死锁的必要条件<br>(1)互斥条件:指进程对所分配到的资源进行排它性使用,即在一段时间内某资源只由一个进程占用。如果此时还有其它进程请求该资源,则请求者只能等待,直至占有该资源的进程用毕释放。<br>(2)请求和保持条件:指进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源又被其它进程占有,此时请求进程阻塞,但又对自己获得的其它资源保持不放。<br>(3)不剥夺条件:指进程已获得资源,在使用完之前,不能被剥夺,只能在使用完时由自己释放。<br>(4)环路等待条件:指在发生死锁时,必然存在一个进程—资源的环形链,即进程集合(P0,P1,P2,…,Pn)中的P0正在等待一个P1占用的资源;P1正在等待一个P2占用的资源,……,Pn正在等待已被P0占用的资源。<br>处理死锁的策略<br>1、忽略该问题。例如鸵鸟算法。<br>2、检测死锁并且恢复。<br>3、仔细地对资源进行动态分配,以避免死锁。<br>4、通过破除死锁四个必要条件之一,来防止死锁产生。<br>鸵鸟算法:<br>该算法可以应用在极少发生死锁的的情况下。为什么叫鸵鸟算法呢,因为传说中鸵鸟看到危险就把头埋在地底下,可能鸵鸟觉得看不到危险也就没危险了吧。跟掩耳盗铃有点像。<br>银行家算法:<br> 所谓银行家算法,是指在分配资源之前先看清楚,资源分配后是否会导致系统死锁。如果会死锁,则不分配,否则就分配。<br>按照银行家算法的思想,当进程请求资源时,系统将按如下原则分配系统资源:<br>(1) 当一个进程对资源的最大需求量不超过系统中的资源数时可以接纳该进程。<br>(2) 进程可以分期请求资源,当请求的总数不能超过最大需求量。<br>(3) 当系统现有的资源不能满足进程尚需资源数时,对进程的请求可以推迟分配,但总能使进程在有限的时间里得到资源。<br>(4) 当系统现有的资源能满足进程尚需资源数时,必须测试系统现存的资源能否满足该进程尚需的最大资源数,若能满足则按当前的申请量分配资源,否则也要推迟分配。<br>解决死锁的策略<br>对待死锁的策略主要有:<br>(1) 死锁预防:破坏导致死锁必要条件中的任意一个就可以预防死锁。例如,要求用户申请资源时一次性申请所需要的全部资源,这就破坏了保持和等待条件;将资源分层,得到上一层资源后,才能够申请下一层资源,它破坏了环路等待条件。预防通常会降低系统的效率。<br>(2) 死锁避免:避免是指进程在每次申请资源时判断这些操作是否安全,例如,使用银行家算法。死锁避免算法的执行会增加系统的开销。<br>(3) 死锁检测:死锁预防和避免都是事前措施,而死锁的检测则是判断系统是否处于死锁状态,如果是,则执行死锁解除策略。<br>(4) 死锁解除:这是与死锁检测结合使用的,它使用的方式就是剥夺。即将某进程所拥有的资源强行收回,分配给其他的进程。</p>
<p>死锁的避免:<br>死锁的预防是通过破坏产生条件来阻止死锁的产生,但这种方法破坏了系统的并行性和并发性。<br>死锁产生的前三个条件是死锁产生的必要条件,也就是说要产生死锁必须具备的条件,而不是存在这3个条件就一定产生死锁,那么只要在逻辑上回避了第四个条件就可以避免死锁。<br>避免死锁采用的是允许前三个条件存在,但通过合理的资源分配算法来确保永远不会形成环形等待的封闭进程链,从而避免死锁。该方法支持多个进程的并行执行,为了避免死锁,系统动态的确定是否分配一个资源给请求的进程。方法如下:<br>1.如果一个进程的当前请求的资源会导致死锁,系统拒绝启动该进程;<br>2.如果一个资源的分配会导致下一步的死锁,系统就拒绝本次的分配;<br>显然要避免死锁,必须事先知道系统拥有的资源数量及其属性</p>
</blockquote>
<p><strong>多线程编程的时候,使用无锁结构会不会比有锁结构更加快?</strong></p>
<blockquote>
<p>无论有锁(mutex)还是无锁(lock-free)总是只有一个线程在执行任务,只不过对于lock-free所有线程都可以进临界区。所以从这点上看其实有锁和无锁在性能上应该是一样的。<br>但是从另一点上是有区别的,他们的不同体现在拿不到锁的态度:有锁的情况就是睡觉,无锁的情况就不断spin。<br>睡觉这个动作会陷入内核,发生context switch,这个是有开销的,但是这个开销能有多大呢,当你的临界区很小的时候,这个开销的比重就非常大。这也是为什么临界区很小的时候,换成lockfree性能通常会提高很多的原因。<br>再来看lockfree的spin,一般都遵循一个固定的格式:先把一个不变的值X存到某个局部变量A里,然后做一些计算,计算/生成一个新的对象,然后做一个CAS操作,判断A和X还是不是相等的,如果是,那么这次CAS就算成功了,否则再来一遍。如果上面这个loop里面“计算/生成一个新的对象”非常耗时并且contention很严重,那么lockfree性能有时会比mutex差。另外lockfree不断地spin引起的CPU同步cacheline的开销也比mutex版本的大。<br>lockfree的意义不在于绝对的高性能,它比mutex的优点是使用lockfree可以避免死锁/活锁,优先级翻转等问题。但是因为ABA problem、memory order等问题,使得lockfree比mutex难实现得多。</p>
</blockquote>
<p><strong>lock-free的实现方式?</strong></p>
<blockquote>
<p>(1)lock-free定义:多线程中不会导致线程间相互阻塞,称之为lock-free。不使用锁结构,可以降低线程间互相阻塞的机会。<br>(2)所谓lock-free的实现,实际上就是在不使用锁结构的条件下,实现线程安全。实现方式可以采用C++11中的Atomic,。<br>(3)Atomic:<br>Atomic一系列原子操作类,它们提供的方法能保证具有原子性。这些方法是不可再分的,获取这些变量的值时,永远获得修改前的值或修改后的值,不会获得修改过程中的中间数值。<br> 这些类都禁用了拷贝构造函数,原因是原子读和原子写是2个独立原子操作,无法保证2个独立的操作加在一起仍然保证原子性。<br>atomic<t>提供了常见且容易理解的方法:<br>1)store: store是原子写操作<br>2)load: load则是对应的原子读操作<br>3)exchange:允许2个数值进行交换,并保证整个过程是原子的。<br>4)compare_exchange_weak<br>5)compare_exchange_strong<br>compare_exchange_weak和compare_exchange_strong要求在参数中传入期待的数值和新的数值。它们对比变量的值和期待的值是否一致,如果是,则替换为用户指定的一个新的数值。如果不是,则将变量的值和期待的值交换。<br>weak版本允许偶然出乎意料的返回(比如在字段值和期待值一样的时候却返回了false),不过在一些循环算法中,这是可以接受的。通常它比起strong有更高的性能。<br>(4)Atomic的简单使用:<br>定义一个具有原子性操作的链表并在头节点前面插入一个节点:<br>std::atomic<node*> head;<br>node* const new_node=new node(data);<br>new_node->next=head.load();<br>(5)利用Atomic实现一个lock-free的栈:<br>参考博文:<a href="https://blog.csdn.net/alien_taiji/article/details/53404176" target="_blank" rel="external">https://blog.csdn.net/alien_taiji/article/details/53404176</a> <a href="https://www.cnblogs.com/dengzz/p/5686866.html" target="_blank" rel="external">https://www.cnblogs.com/dengzz/p/5686866.html</a></node*></t></p>
</blockquote>
<p><strong>如何证明一个数据结构是线程安全的?</strong></p>
<blockquote>
<p>一个不论运行时(Runtime)如何调度线程都不需要调用方提供额外的同步和协调机制还能正确地运行的类是线程安全的。多线程的场景很多很复杂,难以穷尽地说那些条件下是或者不是线程安全的,但是有一些常用的肯定线程安全的场景:<br>1) 无状态的一定是线程安全的。这个很好理解,因为所谓线程不安全也就是一个线程修改了状态,而另一个线程的操作依赖于这个被修改的状态。<br>2) 只有一个状态,而且这个状态是由一个线程安全的对象维护的,那这个类也是线程安全的。比如你在数据结构里只用一个AtomicLong来作为计数器,那递增计数的操作都是线程安全的,不会漏掉任何一次计数,而如果你用普通的long做++操作则不一样,因为++操作本身涉及到取数、递增、赋值 三个操作,某个线程可能取到了另外一个线程还没来得及写回的数就会导致上一次写入丢失。<br>3) 有多个状态的情况下,维持不变性(invariant)的所有可变(mutable)状态都用同一个锁来守护的类是线程安全的。这一段有些拗口,首先类不变性的意思是指这个类在多线程状态下能正确运行的状态,其次用锁守护的意思是所有对该状态的操作都需要获取这个锁,而用同一个锁守护的作用就是所有对这些状态的修改实际最后都是串行的,不会存在某个操作中间状态被其他操作可见,继而导致线程不安全。所以这里的关键在于如何确定不变性,可能你的类的某些状态对于类的正确运行是无关紧要的,那就不需要用和其他状态一样的锁来守护。因此我们常可以看到有的类里面会创建一个新的对象作为锁来守护某些和原类本身不变性无关的状态。</p>
</blockquote>
<p><strong>C++线程安全的单例类?</strong></p>
<p>利用pthread_once的线程安全单例:<br><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span><span class="meta-string"><pthread.h></span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span><span class="meta-string"><iostream></span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span><span class="meta-string"><stdlib.h></span></span></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> <span class="built_in">std</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">template</span><<span class="keyword">class</span> T></span><br><span class="line"><span class="keyword">class</span> Singleton {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="keyword">static</span> T& <span class="title">instance</span><span class="params">()</span> </span>{</span><br><span class="line"> pthread_once(&once, Init);</span><br><span class="line"> <span class="keyword">return</span> *value_;</span><br><span class="line"> }</span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line"> Singleton();</span><br><span class="line"> ~Singleton();</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">static</span> <span class="keyword">void</span> <span class="title">Destory</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">typedef</span> <span class="keyword">char</span> COMPELETE_TYPE[(<span class="keyword">sizeof</span>(T) == <span class="number">0</span>)? <span class="number">-1</span> : <span class="number">1</span>]</span><br><span class="line"> COMPELETE_TYPE dommy; <span class="comment">//不是完整类型 sizeof(T) 为-1 ,那么这里定义的时候 [-1]会触发报错</span></span><br><span class="line"> (<span class="keyword">void</span>) dommy; <span class="comment">//查看是否是完整类型,如果不是完整类型,不能实例化它</span></span><br><span class="line"> <span class="built_in">cout</span><<<span class="string">"Destory"</span> << <span class="built_in">endl</span>;</span><br><span class="line"> <span class="keyword">delete</span> value_;</span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="keyword">static</span> <span class="keyword">void</span> <span class="title">Init</span><span class="params">(<span class="keyword">void</span>)</span> </span>{</span><br><span class="line"> value_ = <span class="keyword">new</span> T();</span><br><span class="line"> <span class="built_in">cout</span> << <span class="string">"Init "</span><<<span class="built_in">endl</span>;</span><br><span class="line"> atexit(Destory);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">static</span> T * value_;</span><br><span class="line"> <span class="keyword">static</span> <span class="keyword">pthread_once_t</span> once;</span><br><span class="line">};</span><br><span class="line"><span class="keyword">template</span><<span class="keyword">class</span> T></span><br><span class="line">T * Singleton<T>::value_ = <span class="literal">NULL</span>;</span><br><span class="line"><span class="keyword">template</span><classT></span><br><span class="line">Singleton<T>:: once == PTHREAD_ONCE_INIT;</span><br></pre></td></tr></table></figure></p>
<p>饿汉模式:<br><figure class="highlight autoit"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">class <span class="built_in">Singleton</span> {</span><br><span class="line">private:</span><br><span class="line"> <span class="keyword">static</span> <span class="keyword">const</span> <span class="built_in">Singleton</span>* m_instance<span class="comment">;</span></span><br><span class="line"> <span class="built_in">Singleton</span>(){}</span><br><span class="line">public:</span><br><span class="line"> <span class="keyword">static</span> <span class="keyword">const</span> <span class="built_in">Singleton</span>* getInstance() {</span><br><span class="line"> <span class="keyword">return</span> m_instance<span class="comment">;</span></span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="built_in">Singleton</span>* <span class="built_in">Singleton</span>::m_instance = new <span class="built_in">Singleton</span><span class="comment">;</span></span><br></pre></td></tr></table></figure></p>
<p><strong>多线程环境带有状态的对象的讨论?</strong></p>
<blockquote>
<p>如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,那么就是线程安全的。<br>或者说,一个类或者程序所提供的接口对于线程来说是原子操作或者多个线程之间的切换不会导致该接口的执行结果存在二义性,也就是说我们不用考虑同步的问题。<br>线程安全问题都是由全局变量及静态变量引起的。 若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则就可能影响线程安全。<br>2)关于线程安全<br>常量始终是线程安全的,因为只存在读操作。<br>每次调用方法前都新建一个实例是线程安全的,因为不会访问共享的资源(共享堆内存)。<br>局部变量是线程安全的。因为每执行一个方法,都会在独立的空间创建局部变量,它不是共享的资源。局部变量包括方法的参数变量和方法内变量。<br>3)有状态和无状态对象<br>有状态就是有数据存储功能。有状态对象(Stateful Bean),就是有实例变量的对象 ,可以保存数据,是非线程安全的。其实就是有可写数据成员的对象。<br>无状态就是一次操作,不能保存数据。无状态对象(Stateless Bean),就是没有实例变量的对象。不能保存数据,是不变类,是线程安全的。具体来说就是只有方法没有数据成员的对象,或者有数据成员但是数据成员是可读的对象。</p>
</blockquote>
<p><strong>程序什么时候应该使用线程,什么时候单线程效率高?</strong></p>
<blockquote>
<p>使用线程可以把占据长时间的程序中的任务放到后台去处理,如用户在界面点击,让一个线程取处理该项操作,在一些等待的任务实现上如用户输入、文件读写和网络收发数据等的情况下,使用线程可以释放掉一些资源如内存占用等等。<br>单线程效率高的情况:对于频繁创建和切换线程可能会造成大的时间开销,这样多线程带来的时间代价将会拖慢整个程序,由此不如单线程来的方便。<br>对于处理时间短的服务或者启动频率高的要用单线程,相反用多线程! </p>
</blockquote>
<p><strong>惊群现象?</strong></p>
<blockquote>
<p>举一个很简单的例子,当你往一群鸽子中间扔一块食物,虽然最终只有一个鸽子抢到食物,但所有鸽子都会被惊动来争夺,没有抢到食物的鸽子只好回去继续睡觉, 等待下一块食物到来。这样,每扔一块食物,都会惊动所有的鸽子,即为惊群。对于操作系统来说,多个进程/线程在等待同一资源时,也会产生类似的效果,其结果就是每当资源可用,所有的进程/线程都来竞争资源,造成的后果:<br>1)系统对用户进程/线程频繁的做无效的调度、上下文切换,系统性能大打折扣。<br>2)为了确保只有一个线程得到资源,用户必须对资源操作进行加锁保护,进一步加大了系统开销。<br>关于“线程池中惊群问题”:<br>在任务队列加入新的任务后,使用pthread_cond_signal而不是使用pthread_cond_broadcast()函数来通知等待的线程队列,这样可靠的保证了,每次只通知到一个等待线程,避免了多个线程同时抢占资源的问题。</p>
</blockquote>
<h2 id="多线程编程"><a href="#多线程编程" class="headerlink" title="多线程编程"></a>多线程编程</h2><p><strong>Linux 开发,使用多线程还是用 IO 复用 select/epoll?</strong></p>
<blockquote>
<p>多线程模型适用于处理短连接,且连接的打开关闭非常频繁的情形,但不适合处理长连接。多线程模型默认情况下,(在Linux)每个线程会开8M的栈空间,假定有10000个连接,开这么多个线程需要80G的内存空间!即使调整每个线程的栈空间,也很难满足更多的需求。攻击者可以利用这一点发动DDoS,只要一个连接连上服务器什么也不做,就能吃掉服务器几M的内存,这不同于多进程模型,线程间内存无法共享,因为所有线程处在同一个地址空间中。内存是多线程模型的软肋。<br>在UNIX平台下多进程模型擅长处理并发长连接,但却不适用于连接频繁产生和关闭的情形。Windows平台忽略此项。 同样的连接需要的内存数量并不比多线程模型少,但是得益于操作系统虚拟内存的Copy on Write机制,fork产生的进程和父进程共享了很大一部分物理内存。但是多进程模型在执行效率上太低,接受一个连接需要几百个时钟周期,产生一个进程 可能消耗几万个CPU时钟周期,两者的开销不成比例。而且由于每个进程的地址空间是独立的,如果需要进行进程间通信的话,只能使用IPC进行进程间通 信,而不能直接对内存进行访问。在CPU能力不足的情况下同样容易遭受DDos,攻击者只需要连上服务器,然后立刻关闭连接,服务端则需要打开一个进程再关闭。<br>同时需要保持很多的长连接,而且连接的开关很频繁,最高效的模型是非阻塞、异步IO模型。而且不要用select/poll,这两个API的有着O(N)的时间复杂度。在Linux用epoll,BSD用kqueue,Windows用IOCP,或者用libevent封装的统一接口(对于不同平台libevent实现时采用各个平台特有的API),这些平台特有的API时间复杂度为O(1)。然而在非阻塞,异步I/O模型下的编程是非常痛苦的。由于I/O操作不再阻塞,报文的解析需要小心翼翼,并且需要亲自管理维护每个链接的状态。并且为了充分利用CPU,还应结合线程池,避免在轮询线程中处理业务逻辑。<br>参考知乎:<a href="http://www.zhihu.com/question/20114168/answer/14024115" target="_blank" rel="external">http://www.zhihu.com/question/20114168/answer/14024115</a></p>
</blockquote>
<p><strong>开发多线程的程序应该注意哪些问题?</strong></p>
<blockquote>
<p>多线程的主要是需要处理大量的IO操作或者处理的情况需要花大量的时间等等,比如读写文件,网络数据接收,视频图像的采集,处理显示保存等操作缓慢的情形和需大幅度的提高性能的程序中使用。<br>但也不是都使用多线程,因为多线程过多的线程一般会导致数据共享问题,太多多线程切换也是会影响性能的,所以一般不须采用多线程的不用多线程效果更好。<br>1 线程间通信<br>线程间通信主要涉及在线程间传递数据,或相互通知某些事件的完成。可采用的方法包括:<br>方法1:全局变量<br>方法2:发送消息<br>2 线程间的同步与互斥<br>多线程会涉及对共享资源或独占资源的访问,也就引入了同步与互斥的问题。<br>假设多个线程都要修改同一个全局变量:<br>互斥——同一时刻只能有一个线程修改该变量,但谁先处理谁后处理无所谓。<br>同步——同一时刻只能有一个线程修改该变量,但其中一个线程要等待另一个线程处理完之后才能处理。<br>即,同步是有先后顺序要求的互斥。<br>3 线程使用中要注意,如何控制线程的调度和阻塞,例如利用事件的触发来控制线程的调度和阻塞,也有用消息来控制的。<br>4 线程中如果用到公共资源,一定要考虑公共资源的线程安全性。一般用LOCK锁机制来控制线程安全性。一定要保证不要有死锁机制。<br>5 线程的终止一般要使线程体在完成一件工作的情况下终止,一般不要直接使用抛出线程异常的方式终止线程。<br>6 线程的优先级一定根据程序的需要要有个整体的规划。<br>7 注意条件返回时互斥锁的解锁问题<br>8 正确处理 Linux 平台下的线程结束问题<br>在 Linux 平台下,当处理线程结束时需要注意的一个问题就是如何让一个线程善始善终,让其所占资源得到正确释放。在 Linux 平台默认情况下,虽然各个线程之间是相互独立的,一个线程的终止不会去通知或影响其他的线程。但是已经终止的线程的资源并不会随着线程的终止而得到释放,我们需要调用 pthread_join() 来获得另一个线程的终止状态并且释放该线程所占的资源<br>9 等待的绝对时间问题<br>超时是多线程编程中一个常见的概念。例如,当你在 Linux 平台下使用 pthread_cond_timedwait() 时就需要指定超时这个参数,以便这个 API 的调用者最多只被阻塞指定的时间间隔。</p>
</blockquote>
<p><strong>多线程网络编程中如何合理地选择线程数?</strong></p>
<blockquote>
<p>首先确定应用是CPU密集型 (例如分词,加密等),还是耗时io( 网络,文件操作等)<br>CPU密集型:最佳线程数等于cpu核心数或稍微小于cpu核心数。<br>耗时io型:最佳线程数一般会大于cpu核心数很多倍。一般是io设备延时除以cpu处理延时,得到一个倍数,我的经验数值是20–50倍*cpu核心数,保证线程空闲可以衔接上。<br>最佳线程数量也与机器配置(内存,磁盘速度)有关,如果cpu,内存,磁盘任何一个达到顶点,就需要适当减少线程数。</p>
</blockquote>
<p>如有误,欢迎批评指正!</p>
<p>计算机程序的运行离不开进程和线程,进程和线程也是面试中常常要抠的部分。本文梳理一下进程和线程的一些常见问题,包括其基本概念,区别于联系,高级主题包括线程安全,多线程编程等等。</p>
<p>[TOC]</p>
<h2 id="基本概念"><a href="#基本概念" cl
计算机网络常见基础问题
http://whatbeg.com/2019/06/05/computernetwork.html
2019-06-05T03:25:13.000Z
2019-06-05T03:41:45.976Z
<p>通常现在的计算机网络都采用一种分层模型来构建,本文也以分层的结构来梳理计算机网络常见面试问题。</p>
<h2 id="层次结构总述"><a href="#层次结构总述" class="headerlink" title="层次结构总述"></a>层次结构总述</h2><p><strong>网络体系结构?</strong></p>
<blockquote>
<p>有两种模型,一种是7层网络模型,也就是OSI模型,一种是4层模型,也就是TCP/IP模型。<br>前者模型是从底向上一步一步进行抽象,分别是物理层、数据链路层、网络层、传输层、会话层、表示层,应用层。具体如下图表示,高层是底层的一种抽象。</p>
<p>4层模型是将7层模型进行了浓缩,将底下两层,合并成为了数据链路层,网络层和传输层单独,最上面3层合并成为了应用层,具体对应关系如下图所示</p>
<p>一般现在用到的都是TCP/IP 4层网络模型,和7层模型相比,要简单不少,同时7层模型分层分的太过于细腻,导致实现起来也比较麻烦,而4层模型已经可以较好的满足现实世界中的业务模型,因此 TCP/TP 4 层模型成为了事实上的标准。</p>
</blockquote>
<p><strong>OSI模型中,一个协议应该属于哪一层是以什么为标准划分的?</strong></p>
<blockquote>
<p>一个协议归属于OSI参考模型哪一层,主要根据该层可以提供什么样的服务。这个服务如果拘泥于一条链路,则为数据链路层;如果服务可以让终端的流量可以跨越路由器的不同接口在互联网穿梭,则为网络层。如果可以提供或可靠、或不可靠端对端服务的,则为传输层。可以给用户提供服务的则为应用层。</p>
</blockquote>
<p>最好一个全流程理解计算机网络的例子如下:</p>
<p><strong>浏览器中输入一个URL发生什么,用到那些协议?</strong></p>
<blockquote>
<p>浏览器中输入URL,首先浏览器要将URL解析为IP地址,解析域名就要用到DNS协议,首先主机会查询DNS的缓存,如果没有就给本地DNS发送查询请求。DNS查询分为两种方式,一种是递归查询,一种是迭代查询。如果是迭代查询,本地的DNS服务器,向根域名服务器发送查询请求,根域名服务器告知该域名的一级域名服务器,然后本地服务器给该一级域名服务器发送查询请求,然后依次类推直到查询到该域名的IP地址。DNS服务器是基于UDP的,因此会用到UDP协议。<br>得到IP地址后,浏览器就要与服务器建立一个http连接。因此要用到http协议,http协议报文格式上面已经提到。http生成一个get请求报文,将该报文传给TCP层处理。如果采用https还会先对http数据进行加密。TCP层如果有需要先将HTTP数据包分片,分片依据路径MTU和MSS。TCP的数据包然后会发送给IP层,用到IP协议。IP层通过路由选路,一跳一跳发送到目的地址。当然在一个网段内的寻址是通过以太网协议实现(也可以是其他物理层协议,比如PPP,SLIP),以太网协议需要直到目的IP地址的物理地址,有需要ARP协议。</p>
</blockquote>
<p><img src="https://blog-image-1256228880.cos.ap-beijing.myqcloud.com/process.jpg" alt=""></p>
<h2 id="应用层"><a href="#应用层" class="headerlink" title="应用层"></a>应用层</h2><p>应用层包括两个主要的协议:HTTP 和 DNS</p>
<p><strong>HTTP/HTTPS 1.0/1.1/2.0的特点和区别?</strong></p>
<blockquote>
<p>HTTPS是基于安全套接字层的超文本传输协议,HTTPS在HTTP应用层的基础上使用安全套接字层作为子层。HTTPS = HTTP + SSL。</p>
<p>HTTP和HTTPS的区别如下:<br>(1) 安全性:HTTP是超文本传输协议,信息是明文传输,容易发生流量劫持和HTTP攻击,HTTPS进行SSL加密传输、身份认证,比HTTP安全;<br>(2) 费用:HTTP免费,HTTPS的CA证书需要一定费用;<br>(3) 端口:HTTP标准端口为80,HTTPS标准端口号为443。<br>(4) 防劫持:HTTPS可以有效的防止运营商劫持,解决了防劫持的一个大问题。</p>
<p>HTTP1.0/1.1/2.0的特点和区别如下:<br>(1) HTTP1.0规定浏览器与服务器保持较短时间的链接,链接无法复用,易产生线头阻塞;<br>(2) HTTP1.1支持持久链接,增加请求头和响应头来扩充功能,包括断点续传、缓存处理和host头处理;<br>(3) HTTP2.0支持多路复用、首部压缩、流量控制和服务端推送。</p>
</blockquote>
<p><strong>HTTP中,session 和 cookie 区别?禁用cookie后怎么办?</strong></p>
<blockquote>
<p>Session是由应用服务器维持的一个服务器端的存储空间,用户在连接服务器时,会由服务器生成一个唯一的SessionID,用该SessionID 为标识符来存取服务器端的Session存储空间。而SessionID这一数据则是保存到客户端,用Cookie保存的,用户提交页面时,会将这一 SessionID提交到服务器端,来存取Session数据。这一过程,是不用开发人员干预的。所以一旦客户端禁用Cookie,那么Session也会失效。<br>Cookies是服务器在本地机器上存储的小段文本并随每一个请求发送至同一个服务器。IETF RFC 2965 HTTP State Management Mechanism 是通用cookie规范。网络服务器用HTTP头向客户端发送cookies,在客户终端,浏览器解析这些cookies并将它们保存为一个本地文件,它会自动将同一服务器的任何请求缚上这些cookies。</p>
<p>那么,在客户端禁用Cookie的时候,我们要怎么做呢,可以有以下两种方法<br>1)设置php.ini中的session.use_trans_sid = 1或者在PHP编译时打开–enable-trans-sid选项,让PHP自动通过URL传递session id。<br>2)如果是虚拟主机或者租用的服务器,无法去修改PHP.ini,那么可以手动通过URL传值,或者通过隐藏表单传递session id。说简单些就是自己去操纵sessionid这个唯一标识符,去鉴别用户即可。</p>
</blockquote>
<p><strong>域名解析的过程?DNS的工作过程?</strong></p>
<blockquote>
<p>1)检查浏览器缓存中有没有<br>2)检查本地系统hosts文件中有没有<br>3)检查本地路由器中有没有<br>4)ISP的本地DNS服务器:ISP是互联网服务提供商(Internet Service Provider)的简称,ISP有专门的DNS服务器应对DNS查询请求。<br>5)根服务器:ISP的DNS服务器还找不到的话,它就会向根服务器发出请求,进行递归查询(DNS服务器先问根域名服务器.com域名服务器的IP地址,然后再问.com域名服务器,依次类推)。</p>
</blockquote>
<p><strong>DNS欺骗的方式?</strong></p>
<blockquote>
<p>hosts文件篡改<br>本机DNS劫持<br>DNS通讯包篡改<br>SYN Flood:SYN-FLOOD是一种常见的DDos攻击,拒绝服务攻击。通过网络服务所在的端口发送大量伪造原地址的攻击报文,发送到服务端,造成服务端上的半开连接队列被占满,从而阻止其他用户进行访问。<br>它的数据报特征是大量syn包,并且缺少最后一步的ACK回复。</p>
</blockquote>
<h2 id="传输层"><a href="#传输层" class="headerlink" title="传输层"></a>传输层</h2><p>传输层是重头戏,其中包括两个主要协议TCP,UDP。其中TCP提供可靠的有保证的传输,涉及的知识点比较多,面试中也被问得比较多。</p>
<p><strong>TCP 和 UDP 有什么区别?</strong></p>
<blockquote>
<p>最基本的是基于连接和无连接的区别,这是因为协议的目的是不一样的。TCP实现的是可靠传输,因此就需要进行拥塞控制和流量控制,超时重传,乱序分包进行重排等相关实现。而UDP是没有连接的概念,只管发送出去,也不管是否可达或者是是否可以收到回应报文。因此这就体现在TCP报文首部需要20个字节以上,而UDP报文首都需要8个字节就可以了。<br>TCP与UDP区别总结:<br>1)TCP面向连接(如打电话要先拨号建立连接); UDP是无连接的,即发送数据之前不需要建立连接<br>2)TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保证可靠交付<br>3)TCP面向字节流,实际上是TCP把数据看成一连串无结构的字节流; UDP是面向报文的,UDP没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低(对实时应用很有用,如IP电话,实时视频会议等)<br>4)每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信<br>5)TCP首部开销20字节(32bit x 5); UDP的首部开销小,只有8个字节(16位源端口,16位目的端口,16位长度,16位校验和)<br>6)TCP的逻辑通信信道是全双工的可靠信道,UDP则是不可靠信道</p>
</blockquote>
<p><strong>TCP 和 UDP 的应用场景?</strong></p>
<blockquote>
<p>从特点上,TCP 是可靠的但传输速度慢 ,UDP 是不可靠的但传输速度快。因此在选用具体协议通信时,应该根据通信数据的要求而决定。<br> 若通信数据完整性需让位与通信实时性,则应该选用 TCP 协议(如文件传输、重要状态的更新等);反之,则使用 UDP 协议(如视频传输、实时通信等)。<br> <br><strong>如何实现可靠的UDP?</strong></p>
<p>自定义通讯协议,在应用层定义一些可靠的协议,比如检测包的顺序,重复包等问题,如果没有收到对方的ACK,重新发包。<br>UDP没有Delievery Garuantee,也没有顺序保证,所以如果你要求你的数据发送与接受既要高效,又要保证有序,收包确认等,你就需要在UDP协议上构建自己的协议。比如RTCP,RTP协议就是在UPD协议之上专门为H.323协议簇上的IP电话设计的一种介于传输层和应用层之间的协议。</p>
<p>简单来讲,要使用UDP来构建可靠的面向连接的数据传输,就要实现类似于TCP协议的超时重传,有序接受,应答确认,滑动窗口流量控制等机制,等于说要在传输层的上一层(或者直接在应用层)实现TCP协议的可靠数据传输机制,比如使用UDP数据包+序列号,UDP数据包+时间戳等方法,在服务器端进行应答确认机制,这样就会保证不可靠的UDP协议进行可靠的数据传输,不过这好像也是一个难题!</p>
</blockquote>
<p><strong>UDP中一个包的大小最大能多大?</strong></p>
<blockquote>
<p>相对于不同的系统,不同的要求,其得到的答案是不一样的。<br>1)局域网环境下,建议将UDP数据控制在1472字节以下。<br>以太网(Ethernet)数据帧的长度必须在46-1500字节之间,这是由以太网的物理特性决定的,这个1500字节被称为链路层的MTU(最大传输单元)。但这并不是指链路层的长度被限制在1500字节,其实这个MTU指的是链路层的数据区,并不包括链路层的首部和尾部的18个字节。所以,事实上这个1500字节就是网络层IP数据报的长度限制。因为IP数据报的首部为20字节,所以IP数据报的数据区长度最大为1480字节。而这个1480字节就是用来放TCP传来的TCP报文段或UDP传来的UDP数据报的。又因为UDP数据报的首部8字节,所以UDP数据报的数据区最大长度为1472字节。这个1472字节就是我们可以使用的字节数。<br>当我们发送的UDP数据大于1472的时候会怎样呢? 这也就是说IP数据报大于1500字节,大于MTU,这个时候发送方IP层就需要分片(fragmentation)。把数据报分成若干片,使每一片都小于MTU,而接收方IP层则需要进行数据报的重组。这样就会多做许多事情,而更严重的是,<strong>由于UDP的特性,当某一片数据传送中丢失时,接收方无法重组数据报,将导致丢弃整个UDP数据报。</strong>因此,在普通的局域网环境下,建议将UDP的数据控制在1472字节以下为好。</p>
<p>2)Internet编程时,建议将UDP数据控制在548字节以下<br>进行Internet编程时则不同,因为Internet上的路由器可能会将MTU设为不同的值。如果我们假定MTU为1500来发送数据,而途经的某个网络的MTU值小于1500字节,那么系统将会使用一系列的机制来调整MTU值,使数据报能够顺利到达目的地,这样就会做许多不必要的操作。鉴于Internet上的标准MTU值为576字节,所以我建议在进行Internet的UDP编程时,最好将UDP的数据长度控件在548字节以内。</p>
</blockquote>
<p>下面说重头戏,TCP:</p>
<p><strong>详细说明TCP状态迁移过程?</strong></p>
<blockquote>
<p>客户端:CLOSED初始态,发送SYN,进入SYN_SENT,收到SYN+ACK,发送ACK,进入ESTABLISHED状态,然后需要结束通信,发送FIN,进入FIN_WAIT1,收到服务器的ACK,进入FIN_WAIT2,收到服务器的FIN,发送ACK,进入TIME_WAIT,结束进入(回到)CLOSED状态。<br>服务端:CLOSED初始态,进入LISTEN状态,收到SYN,发送SYN+ACK,进入SYN_RCVD状态,收到ACK(第三次握手),进入ESTABLISHED状态,收到FIN,发送ACK,进入CLOSED_WAIT状态,等自己数据发完,发送FIN,进入LAST_ACK状态,如果成功,进入(返回)CLOSED状态。</p>
</blockquote>
<p><strong>TCP三次握手和四次挥手,以及各个状态的作用?</strong></p>
<blockquote>
<p>三次握手用于进行连接<br>第一次握手:建立连接时,客户端发送SYN包,例如seq = 200 ,SYN = 1 (200是随机产生的一个值,TCP规定SYN = 1的时候不能携带数据)给服务器,然后Socket进入SYN_SENT状态<br>第二次握手:服务器收到SYN报文段进行确认,发一个将SYN=1,ACK = 1,seq = 300,ack = 201(ack确认号为收到的序列号+1,ACK=1表示确认号有效,建立成功全程都是1,300是随机生成的)<br>第三次握手:客户端再进行一次确认,发一个ACK = 1,seq = 201,SYN = 0,ack = 301,ACK = 1(seq为第一次发送的seq+1,SYN从此都是0,ack为服务器确认的seq+1,ACK从此都是1)<br>建立成功 之后就可以相互发送数据报文了。<br>四次挥手用于断开连接<br>tcp四次挥手,由于TCP连接是全双工的,因此每个方向都必须单独进行关闭。这个原则是当一方完成它的数据发送任务后就能发送一个FIN来终止这个方向的连接。收到一个 FIN只意味着这一方向上没有数据流动,一个TCP连接在收到一个FIN后仍能发送数据。首先进行关闭的一方将执行主动关闭,而另一方执行被动关闭。<br>第一次挥手: 机1向主机2发送FIN报文段,表示关闭数据传送,并主机1进入FIN_WAIT_1状态,表示没有数据要传输了<br>第二次挥手:机2接受到FIN报文后进入CLOSE_WAIT状态(被动关闭),然后发送ACK确认,表示同意了主机1的关闭请求,这个时候主机1进入到FIN_WAIT_2状态,这个状态是相对来讲稍微持久一点的。<br>第三次挥手:机2等待主机1发送完数据,发送FIN到主机1请求关闭,主机2进入LAST_ACK状态,<br>第四次挥手:机1收到主机2发送的FIN后,回复ACK确认到主机2,主机1进入TIME_WAIT状态,主机2收到主机1的ACK后就关闭连接了,状态为CLOSED,主机1等待2MSL,仍然没有收到主机2的回复,说明主机2已经正常关闭了,主机1关闭连接<br>MSL(Maximum Segment Lifetime):报文最大生存时间,是任何报文段被丢弃前在网络内的最长时间。当主机1回复主机2的FIN后,等待(2-4分钟),即使两端的应用程序结束。</p>
</blockquote>
<p><strong>三次握手为什么不是两次或者四次?</strong></p>
<blockquote>
<p>两次太少,因为最后的客户端发往服务器的ACK至为重要,因为两次的话,B无法确定B的信息A是否能收到。<br>四次太多,因为服务器发送的SYN和对客户端的ACK可以合并为一个报文段发送,因为中间没有任何其他数据传输。如果四次,那么就造成了浪费,因为在三次结束之后,就已经可以保证A可以给B发信息,A可以收到B的信息; B可以给A发信息,B可以收到A的信息。</p>
</blockquote>
<p><strong>2MSL是什么状态?作用是什么?</strong></p>
<blockquote>
<p>是TIME_WAIT,主要有两个作用:<br>1)保留端口不被复用,以接受服务器可能重传的FIN。如果不这样做,那么客户端收到重发的FIN会返回一个RST报文段,而服务器期待的是正常的ACK,所以会报错。<br>2)保证双向的包都在网络中消失。如果不这样做,在此端口立马启动另一个相同/相似的连接,则他们可能会收到之前连接的报文段,显然不好,2MSL就能保证之前的连接的所有报文段都在网络中消失。</p>
</blockquote>
<p><strong>time_wait,close_wait状态产生的原因,keepalive?</strong></p>
<blockquote>
<p>TCP协议规定,对于已经建立的连接,网络双方要进行四次握手才能成功断开连接,如果缺少了其中某个步骤,将会使连接处于假死状态,连接本身占用的资源不会被释放。网络服务器程序要同时管理大量连接,所以很有必要保证无用连接完全断开,否则大量僵死的连接会浪费许多服务器资源。在众多TCP状态中,最值得注意的状态有两个:CLOSE_WAIT和TIME_WAIT。</p>
<p>CLOSE_WAIT 是被动关闭连接时形成的。根据TCP状态机,服务器端收到客户端发送的FIN,则按照TCP实现发送ACK,因此进入CLOSE_WAIT状态。但如果服务器端不执行close(),就不能由CLOSE_WAIT迁移到LAST_ACK,则系统中会存在很多CLOSE_WAIT状态的连接。此时,可能是系统忙于处理读、写操作,而未将已收到FIN的连接,进行close。此时,recv/read已收到FIN的连接socket,会返回0。</p>
<p>解决CLOSE_WAIT的方法:<br>1 一般原因都是TCP连接没有调用关闭方法。需要应用来处理网络链接关闭。<br>2 对于Web请求出现这个原因,经常是因为Response的BodyStream没有调用Close。<br>3 TCP的KeepLive功能,可以让操作系统替我们自动清理掉CLOSE_WAIT的连接。但是KeepLive在Windows操作系统下默认是7200秒,也就是2个小时才清理一次。往往满足不了要求。可以调小该数值。</p>
<p>close_Wait引发的问题: close_Wait会占用一个连接,网络可用连接小。数量过多,可能会引起网络性能下降,并占用系统非换页内存。尤其是在有连接池的情况下(比如HttpRequest)会耗尽连接池的网络连接数,导致无法建立新的网络连接。</p>
</blockquote>
<p><strong>大量TIME_WAIT存在什么问题,如何解决?</strong></p>
<blockquote>
<p>ip_local_port_range范围不到3w,大量的TIME_WAIT状态使得local port在TIME_WAIT持续期间不能被再次分配,即没有可用的local port,这将是导致新建连接失败的最大原因。</p>
<p>解决方案:<br>1)修改系统配置<br>具体来说,需要修改本文前面介绍的tcp_max_tw_buckets、tcp_tw_recycle、tcp_tw_reuse这三个配置项。<br>2)修改应用程序<br>具体来说,可以细分为两种方式:<br> 1/ 将TCP短连接改造为长连接。通常情况下,如果发起连接的目标也是自己可控制的服务器时,它们自己的TCP通信最好采用长连接,避免大量TCP短连接每次建立/释放产生的各种开销;如果建立连接的目标是不受自己控制的机器时,能否使用长连接就需要考虑对方机器是否支持长连接方式了。<br> 2/ 通过getsockopt/setsockoptapi设置socket的SO_LINGER选项<br> 3/ 服务器可以设置SO_REUSEADDR套接字选项来通知内核,如果端口忙,但TCP连接位于TIME_WAIT状态时可以重用端口。在一个非常有用的场景就是,如果你的服务器程序停止后想立即重启,而新的套接字依旧希望使用同一端口,此时SO_REUSEADDR选项就可以避免TIME_WAIT状态。</p>
</blockquote>
<p><strong>TCP重传机制?</strong></p>
<blockquote>
<p>TCP是一种可靠的协议,在网络交互的过程中,由于TCP报文是封装在IP协议中的,IP协议的无连接特性导致其可能在交互的过程中丢失,在这种情况下,TCP协议如何保障其传输的可靠性呢?依靠确认/重传机制。<br>重传又分为超时重传和快速重传。<br>1)超时重传<br>说白了就是在请求包发出去的时候,开启一个计时器,当计时器达到时间之后,没有收到ACK,则就进行重发请求的操作,一直重发直到达到重发上限次数或者收到ACK。发送端的等待的时间叫RTO(Retransmission TimeOut).<br>快速重传<br>2)还有一种机制就是快速重传,Fast Retransmit是由接收端主动要求重发的,当接收方收到的数据包是不正常的序列号,那么接收方会重复把应该收到的那一条ACK重复发送,这个时候,如果发送方收到连续3条的同一个序列号的ACK,那么就会启动快速重传机制,把这个ACK对应的发送包重新发送一次。</p>
</blockquote>
<p><strong>TCP拥塞控制?</strong></p>
<blockquote>
<p>TCP 的拥塞控制方法主要有四种:慢开始、拥塞避免、快重传和快恢复。<br>为了简化问题,在下面的讨论中,我们作出如下两个假设:<br>a 数据单向传输;<br>b 接收方的缓存是无限大的,窗口大小仅由网络的拥塞程度决定;<br>(1) 慢开始和拥塞避免<br>这里存在这样一个原则:只要网络没有出现拥塞,拥塞窗口 cwnd(可以理解为发送窗口)就可以无限增大,以便于单次发送更多的数据,但是只要网络发生了拥塞,就必须把拥塞窗口减小,以减少涌入网络的数据量,从而减轻网络的拥塞程度。<br>但是发送方如何知道网络发生了拥塞呢?当网络发生拥塞时,路由器的缓存便处于爆满状态,此时它不得不丢弃一部分分组。这样发送方就不可能收到确认报文,最终导致等待超时。现代网络线路的传输质量都很好,因为硬件层面的问题导致丢弃分组的情况是很少发生的。也就是说,出现超时便代表着网络出现了拥塞。<br>本来是通过控制拥塞窗口的大小来实现 TCP 的拥塞控制,但是为了更好地解释慢开始的原理,我们将报文段的个数作为拥塞窗口的单位,即从原来的增加窗口大小变为了增加报文段的个数。这样可以用较小的数字来说明拥塞控制的原理。<br>慢开始算法的“慢”不是指增长慢,而是指最开始的时候,从一个较低的起点开始增长。<br>值得注意的是,上面的结论并不具有实际意义,因为拥塞窗口只要收到一个新的确认报文就会增大(假如这个确认报文段使得窗口增大),并且立即发送新窗口中的数据,而不必等到该轮次中所有的报文段都成功确认。<br>同时为了避免拥塞窗口无限增大造成网络拥塞,需要设置一个慢开始门限 ssthresh。拥塞窗口 cwnd 的大小和慢开始门限 ssthresh 之间存在如下的关系:<br>a 当 cwnd < ssthresh 时,使用慢开始算法;<br>b 当 cwnd > ssthresh 时,停止使用慢开始算法而改用拥塞避免算法;<br>在慢开始阶段,拥塞窗口的大小呈指数增加;<br>到达慢开始门限后,采用拥塞避免算法,窗口大小依次加 1,呈线性增长趋势;<br>当窗口 cwnd = 24 时,出现了超时,此时发送方判断网络发生了阻塞,于是调整 ssthresh = cwnd/2 = 12,同时重新设置窗口 cwnd = 1;<br>接下来继续慢开始算法,达到新的门限 ssthresh = 12 时改用拥塞避免算法,窗口值呈线性增大;<br>当窗口值 cwnd = 16 时,出现了新的情况:发送方连续收到了 3 个对于同一报文段的重复确认(图中的 3-ACK),这个问题将在快重传和快恢复中继续讨论;</p>
</blockquote>
<p><img src="https://blog-image-1256228880.cos.ap-beijing.myqcloud.com/crowed.png" alt=""></p>
<blockquote>
<p>(3) 快重传和快恢复<br>我们先考虑这样一种情况:有时候,确认报文段只是在网络传输中丢失了,但是其实网络并没有发生拥塞。由于发送方接收不到确认报文段而导致超时的发生,此时便会开启慢开始算法,然后将窗口置为 1,导致传输效率降低。<br>在下面的图中,发送方先发送了 M1 和 M2,并且成功收到了它们的确认报文段,但是接下来发送的 M3 却丢失了。由于接受方此时并不知道发送方向其发送了 M3,所以此时它做出任何响应。当接受方接收到发送方继续发送的 M4 的时候,经过的简单对比,它知道 M3 肯定是丢失了,于是立刻向发送方发出对 M2 的重复确认,对于后面的 M5、M6 也是如此。于是接收方一共收到了 4 个对 M2 的确认,除去第一个之外,后面的三个都是重复确认,也即图中的 3-ACK。此时,快重传算法规定,当发送方连续收到 3 个重复确认的时候,就必须立即对丢失的报文段进行重传(即快重传),这样便不会导致超时的发生,从而避免了之前的情况。<br>接下来对于图中的 3-ACK 点来说,发送方知道只是丢失了部分报文段,于是执行快恢复算法,调整 ssthresh = cwnd/2 = 8,同时设置 cwnd = ssthresh = 8,然后接着开始执行拥塞避免算法。</p>
<p>另外,我们在刚开始的时候,假定发送窗口的大小只会受到网络的影响。但是接收方的缓存肯定是有限的,也就是说:接收窗口 rwnd 和拥塞窗口 cwnd 二者共同制约着发送窗口的大小。显然,三者之间存在下图中的关系:</p>
<p>发送窗口上限 = Min[rwnd, cwnd]</p>
<p>上述公式说明,每次对发送窗口进行调整时,需要根据当前的网络状况和接收窗口的大小而定。</p>
</blockquote>
<p><strong>TCP的滑动窗口?</strong></p>
<blockquote>
<p>滑动窗口是发送方通过接受方的确认报文段中描述的缓冲区的大小来计算自己最多能发送多长的数据。如果发送方收到接受方的窗口大小为0的TCP数据报,那么发送方将停止发送数据,等到接受方发送窗口大小不为0的数据报的到来。<br>缓存与滑动窗口的关系<br>我们在上面的例子中所提到的字节流实际上是位于发、接双方的缓存中的,窗口在字节流上滑动,对发送和接收的字节流进行控制。<br>在发送方这边,发送窗口后沿的字节会被删去,应用进程写入的新的字节又会添加到字节流的最前头。因此必须对应用程序写入字节的速度进行控制,否则会爆缓存。<br>在接收方这边,接收窗口后沿的数据只是在确认报文段中已经确认的数据,等待被应用进程读取,成功读取后才能删除。如果应用进程来不及读取,接收缓存就会被填满,最终导致接收窗口为 0,然后又会反馈到发送窗口那边,使发送停止。<br>值得说明的是,在上面的例子中,我们为了解释的方便,只考虑了一方发送、一方接收的情况。但是由于 TCP 是全双工通信,实际中任意一方都是发送窗口与接收窗口并存的。</p>
</blockquote>
<p><strong>TCP精髓问题:停止等待协议、连续ARQ协议</strong></p>
<blockquote>
<p>停止等待协议<br>停止等待协议是tcp保证传输可靠的重要途径,”停止等待”就是指发送完一个分组就停止发送,等待对方的确认,只有对方确认过,才发送下一个分组.<br>停止等待协议的优点是简单,但是缺点是信道的利用率太低。</p>
<p>连续ARQ协议<br>连续重发请求ARQ方案是指发送方可以连续发送一系列信息帧,即不用等前一帧被确认便可继续发送下一帧,效率大大提高。但在这种重发请求方案中,需要在发送方设置一个较大的缓冲存储空间(称作重发表),用以存放若干待确认的以及待发送信息帧。当发送方收到对某信息帧的确认帧后,便可从重发表中将该信息帧删除。所以,连续重发请求ARQ方案的链路传输效率大大提高,但相应地需要更大的缓冲存储空间。<br>在这一协议中,当发送站点发送完一个数据帧后,不是停下来等待应答帧,而是可以连续再发送若干个数据帧。如果在此过程中又收到了接收端发来的应答帧,那么还可以接着发送数据帧。由于减少了等待时间,整个通信的吞吐量就提高了。<br>ARQ代表的是自动重传请求(Auto Repeat reQuest,ARQ),而GBN与选择重传都属于其中。其中GBN的发送窗口>1,接收窗口=1,选择重传协议:发送窗口大小>1,接收窗口大于1。GBN协议中接收方可以发送累计确认帧ACK,而选择重传没有累计确认的特点。</p>
</blockquote>
<p><strong>说说Nagle算法?</strong></p>
<blockquote>
<p>背景:如果互联网上传递的都是小包,那绝对是个灾难,每个网络请求都耗费比较大的资源,如果一份数据分为零零散散很多份小包,每个网络传输都只传输一个小包,那么是典型的浪费资源,增加拥堵。<br>Nagle算法的基本定义是任意时刻,最多只能有一个未被确认的小段。 所谓“小段”,指的是小于MSS尺寸的数据块,所谓“未被确认”,是指一个数据块发送出去后,没有收到对方发送的ACK确认该数据已收到。<br>Nagle算法规定了,发送方网络链路上一个连接只能有一个未获得ACK的请求包。这个就意味着,发送方只有等待上一个请求的ACK回来之后才能发送下一个请求,这样两个请求过程中间,发送方的缓存区就存储了足够滑动窗口大小的包进行传递,这样就有效避免了大量的小包产生。</p>
</blockquote>
<p><strong>Keepalive是什么东西,如何使用?</strong></p>
<blockquote>
<p>keepalive,是在TCP中一个可以检测死连接的机制。<br>keepalive 原理很简单,TCP会在空闲了一定时间后发送数据给对方:<br>1.如果主机可达,对方就会响应ACK应答,就认为是存活的。<br>2.如果可达,但应用程序退出,对方就发FIN应答,发送TCP撤消连接。<br>3.如果可达,但应用程序崩溃,对方就发RST消息。<br>4.如果对方主机不响应ack, rst,继续发送直到超时,就撤消连接。这个时间就是默认的二个小时。</p>
</blockquote>
<p><strong>Keepalive 和 应用层的 HeartBeat 的联系和区别?</strong></p>
<blockquote>
<p>Keepalive适用于清除死亡时间比较长的连接。<br>1)默认不开启,采用keepalive,它会先要求此连接一定时间没有活动(一般是几个小时),然后发出数据段,经过多次尝试后(每次尝试之间也有时间间隔),如果仍没有响应,则判断连接中断。可想而知,整个周期需要很长的时间(默认时长是2小时)。<br>2)keepalive只能检测连接是否存活,不能检测连接是否可用。比如服务器因为负载过高导致无法响应请求但是双方的连接仍然存在,此时keepalive无法判断该连接是否可用。<br>3)如果TCP连接中的另一方因为停电突然断网等非正常断开的现象,由于服务器端(被动连接/断开的一方)并不知道客户端已断开连接,此时若服务器正在发送数据,那么会导致数据发送失败并进行数据重传,由于重传包的优先级要高于keepalive的数据包,因此keepalive的数据包无法及时发送出去。</p>
<p>所以,需要一种方法能够清除和回收那些在系统不知情的情况下死去了很久的连接,keepalive是非常好的选择。 </p>
<p>在大部分情况下,特别是分布式环境中,我们需要的是一个能够<strong>快速或者实时监控连接状态的机制</strong>,这里,heart-beat才是更加合适的方案。 </p>
<p>它们的不同点在于,keepalive是tcp实现中内建的机制,是在创建tcp连接时通过设置参数启动keepalive机制;而heart-beat则需要在tcp之上的应用层实现。一个简单的heart-beat实现一般测试连接是否中断采用的时间间隔都比较短,可以很快的决定连接是否中断。并且,由于是在应用层实现,因为可以自行决定当判断连接中断后应该采取的行为,而keepalive在判断连接失败后只会将连接丢弃。</p>
</blockquote>
<p><strong>TCP中已有SO_KEEPALIVE选项,为什么还要在应用层加入心跳包机制,或者说,光用keepalive够么?</strong></p>
<blockquote>
<p>主要是因为TCP协议中的SO_KEEPALIVE有几个致命的缺陷:<br>1、keepalive只能检测连接是否存活,不能检测连接是否可用。比如服务器因为负载过高导致无法响应请求但是双方的连接仍然存在,此时keepalive无法判断该连接是否可用。<br>2、如果TCP连接中的另一方因为停电突然断网等非正常断开的现象,由于服务器端(被动连接/断开的一方)并不知道客户端已断开连接,此时若服务器正在发送数据,那么会导致数据发送失败并进行数据重传,由于重传包的优先级要高于keepalive的数据包,因此keepalive的数据包无法及时发送出去。<br>3、当重传超过一定次数,TCP协议会发送keepalive探测包到客户端,一旦探测包没有返回,服务器端会以keepaliveinterval的频率继续发送探测包,经过若干次重试,若服务器一直没有收到应答就会认为该TCP连接已经断开(默认时长是2小时),而2小时以内这个连接一直不会断开,浪费系统资源。</p>
</blockquote>
<p><strong>什么是TCP的自连接,如何解决?</strong></p>
<blockquote>
<p>TCP自连接,就是出现源ip和源端口通目的ip和目的端口完全相同的情况。<br>解决之道:<br>1)可以做一个检测。<br>2)Server端程序的端口号最好不要选择ip_local_port_range区间内的端口,这样Client如果使用随机端口是在ip_local_port_range区间内,这样也就不会发生本机上的自连接。<br>如果Server端的端口号已经固定,并在 ip_local_port_range区间内,那么可以设置 ip_local_reserved_ports 为该Server端的端口号,那么Client就不会使用ip_local_reserved_ports中的值作为随机端口。</p>
</blockquote>
<p><strong>TCP带外数据?</strong></p>
<blockquote>
<p>带外数据即就是优先数据,linux系统的套接字机制支持低层协议发送和接受带外数据。但是TCP协议没有真正意义上的带外数据。为了发送重要协议,TCP提供了一种称为紧急模式(urgent mode)的机制。TCP在报文头中设置URG位,表示进入紧急模式.接收方可以对紧急模式采取特殊的处理。</p>
</blockquote>
<p><strong>Linux 中每个 TCP 连接最少占用多少内存?</strong></p>
<blockquote>
<p>维持一个tcp连接需要占用哪些资源,下面就总结一下最近学习的内容,不足之处,请读者多多指正。<br>一个tcp连接需要:1,socket文件描述符;2,IP地址;3,端口;4,内存<br>TCP连接的四元组:源IP 源端口 目标IP 目标端口,这四元组构成了一个唯一的tcp连接。</p>
<p>对于一台服务器,我们假设只有一个网卡,那么就对应一个唯一的IP地址,而监听端口,我们可以在1024-65535之间任选一个。通过这个监听端口,我们接收来自客户端的连接请求。那么,它的IP、端口已经确定了,下面就是讨论socket文件描述符合内存了。</p>
<p>对于文件描述符fd,每个tcp连接占用一个,那么一个文件描述符下的文件大约占1K字节,而内核对这块也有说明,文件描述符建议最多占用10%的内存,如果是8G内存,那么就相当于800M即80万个文件描述符,当然,这个数据也可以通过linux参数调优进行调节,我在之前的一篇章节中也有讨论到,请大家参考:<a href="http://blog.csdn.net/fox_hacker/article/details/41148115" target="_blank" rel="external">http://blog.csdn.net/fox_hacker/article/details/41148115</a></p>
<p>而对于内存,tcp连接归根结底需要双方接收和发送数据,那么就需要一个读缓冲区和写缓冲区,这两个buffer在linux下最小为4096字节,可通过cat /proc/sys/net/ipv4/tcp_rmem和cat /proc/sys/net/ipv4/tcp_wmem来查看。所以,一个tcp连接最小占用内存为4096+4096 = 8k,那么对于一个8G内存的机器,在不考虑其他限制下,最多支持的并发量为:8<em>1024</em>1024/8 约等于100万。此数字为纯理论上限数值,在实际中,由于linux kernel对一些资源的限制,加上程序的业务处理,所以,8G内存是很难达到100万连接的,当然,我们也可以通过增加内存的方式增加并发量。</p>
<p>网上也有人做过相关试验,程序接收1024000个连接,共消耗7,5G内存,即每个连接消耗在8K左右。</p>
</blockquote>
<p><strong>TCP 能否发送0字节的数据包?</strong></p>
<blockquote>
<p>1)tcp和udp都能发送0字节的数据包,windows下通过GetLastError查看返回值为0<br>2)tcp发送0字节的数据包后,接收方调用recv不会接收到该数据包<br>3)udp发送0字节的数据包后,接收方调用recvfrom能够接收到该数据包</p>
</blockquote>
<p><strong>TCP 协议中为什么syn会消耗一个序号?</strong></p>
<blockquote>
<p>总的来说,tcp是用“确认”这个手段来保证可靠的,在tcp整个过程中,SYN,FIN这两个命令,以及数据传输都是需要确认的,这样才能保证可靠。</p>
</blockquote>
<p><strong>HTTP协议和TCP的区别?</strong></p>
<blockquote>
<p>TCP/IP协议是传输层协议,主要解决数据如何在网络中传输,而HTTP是应用层协议,主要解决如何包装数据。<br>Http协议底层就是使用TCP协议来进行实现的。<br>Http协议帮我们实现了消息边界的划分,因为TCP协议是流式协议,消息是没有边界的,如果希望消息有边界,就需要自己进行消息边界的划分,常用的有两种方式,一种是以\r\n作为消息结束的标志,FTP协议就是使用这种方式,另外一种是包长+包体的形式。HTTP协议采用的混合模式,头部信息是通过\n来进行消息边界的划分,数据信息采用的是包长+包体的格式来进行消息边界的划分。</p>
</blockquote>
<h2 id="网络层"><a href="#网络层" class="headerlink" title="网络层"></a>网络层</h2><p><strong>ARP的机制?</strong></p>
<blockquote>
<p>ARP:地址解析协议,是将IP地址解析为物理地址(MAC地址)的协议,用来解决同一个局域网上的主机或路由器的IP地址和硬件地址的映射问题。ARP高速缓存用来存放本局域网中的各主机和路由器中的IP地址到MAC地址的映射表。ARP缓存表采用了老化机制,在一段时间内如果表中的某一行没有使用,就会被删除,这样可以大大减少ARP缓存表的长度,加快查询速度。</p>
<p>ARP的工作原理:<br>(1) 当源主机要发送数据时,首先检查ARP高速缓存列表中是否有对应IP地址的目的主机的MAC地址,若有,则直接发送数据;若没有,就在本局域网上广播发送ARP请求分组,包括源主机的IP地址、源主机的MAC地址和目的主机的IP地址。<br>(2) 当本局域网中的所有主机收到该ARP请求分组时,首先检查数据包中的目的主机IP地址和自己的IP地址是否一致,若不同,则忽略该数据包;若相同,则将数据包中的源主机的IP地址和MAC地址写入到ARP高速缓存列表中,然后向源主机发送ARP响应分组,同时在此ARP响应包中写入自己的MAC地址。<br>(3) 源主机收到ARP响应分组后,将目的主机的IP地址和MAC地址写入ARP高速缓存列表中,并利用此映射发送数据。<br>广播发送ARP请求分组,单播发送ARP响应分组。</p>
<p>ARP欺骗/攻击<br>ARP攻击就是通过伪造IP地址和MAC地址实现ARP欺骗,能够在网络中产生大量的ARP通信量使网络阻塞,攻击者只要持续不断的发出伪造的ARP响应包就能更改目标主机ARP缓存中的IP-MAC条目,造成网络中断或中间人攻击。ARP攻击主要是存在于局域网网络中,局域网中若有一个人感染ARP木马,则感染该ARP木马的系统将会试图通过“ARP欺骗”手段截获所在网络内其它计算机的通信信息,并因此造成网内其它计算机的通信故障。</p>
</blockquote>
<p><strong>RARP的实现?</strong></p>
<blockquote>
<p>RARP:逆地址解析协议,作用与ARP相反,是将MAC地址解析为IP地址的协议。主要用于无盘工作站引导时获取IP地址。<br>(1) 源端发送一个本地的RARP广播包,在此广播包中声明自己的MAC地址,并且请求任何收到此请求的RARP服务器分配一个IP地址。<br>(2)本地网段上的RARP服务器收到此请求后,检查其RARP列表,查找该MAC地址对应的IP地址。如果存在,RARP服务器就给源端发送一个响应数据包,并将此IP地址提供给对方主机使用;如果不存在,RARP服务器对此不做任何响应。<br>(3)源端在收到从RARP服务器来的响应信息后,利用得到的IP地址进行通信;如果一直没有收到RARP服务器的响应信息,则表示初始化失败。<br>广播发送RARP请求分组,单播发送RARP响应分组。</p>
</blockquote>
<p><strong>IP地址的分类,子网划分?</strong></p>
<blockquote>
<p>A类地址:0开头,1.0.0.0 - 126.255.255.255,其中0和127作为特殊地址。<br>B类地址:10开头,128.0.0.0 - 191.255.255.255。<br>C类地址:110开头,192.0.0.0 - 223.255.255.255。默认子网掩码为255.255.255.0,也可写作/24。最大主机数量256-2=254。<br>D类地址范围:224.0.0.0 - 239.255.255.255。组播、多播<br>E类地址范围:240.0.0.0 - 255.255.255.254。保留地址,255.255.255.255,广播地址</p>
</blockquote>
<p><strong>有哪些私有保留地址?</strong></p>
<blockquote>
<p>私有地址(内部局域网可以使用的)<br>A级:10.0.0.0 - 10.255.255.255<br>B级:172.16.0.0 - 172.31.255.255<br>C级:192.168.0.0 - 192.168.255.255<br>保留地址(特殊用途的)<br>A类:127.X.X.X<br>B类:169.254.X.X</p>
</blockquote>
<p><strong>单播、多播(组播)和广播的区别?</strong></p>
<blockquote>
<p>1)单播:主机之间“一对一”的通讯模式,网络中的交换机和路由器对数据只进行转发不进行复制。<br>2)广播:主机之间“一对所有”的通讯模式,网络对其中每一台主机发出的信号都进行无条件复制并转发,所有主机都可以接收到所有信息(不管你是否需要),由于其不用路径选择,所以其网络成本可以很低廉。<br>3)组播:主机之间“一对一组”的通讯模式,也就是加入了同一个组的主机可以接受到此组内的所有数据,网络中的交换机和路由器只向有需求者复制并转发其所需数据。</p>
</blockquote>
<p><strong>IP组播有什么好处?</strong></p>
<blockquote>
<p>组播是指主机之间“一对一组”的通讯模式,也就是加入了同一个组的主机可以接受到此组内的所有数据,网络中的交换机和路由器只向有需求者复制并转发其所需数据。主机可以向路由器请求加入或退出某个组,网络中的路由器和交换机有选择的复制并传输数据,即只将组内数据传输给那些加入组的主机。这样既能一次将数据传输给多个有需要(加入组)的主机,又能保证不影响其他不需要(未加入组)的主机的其他通讯。</p>
<p>1 需要相同数据流的客户端加入相同的组共享一条数据流,节省了服务器的负载。具备广播所具备的优点。<br>2.由于组播协议是根据接受者的需要对数据流进行复制转发,所以服务端的服务总带宽不受客户接入端带宽的限制。IP协议允许有2亿6千多万个(268435456)组播,所以其提供的服务可以非常丰富。<br>3.此协议和单播协议一样允许在Internet宽带网上传输。 </p>
<p>组播的缺点:<br>1 与单播协议相比没有纠错机制,发生丢包错包后难以弥补,但可以通过一定的容错机制和QOS加以弥补。<br>2 现行网络虽然都支持组播的传输,但在客户认证、QOS等方面还需要完善,这些缺点在理论上都有成熟的解决方案,只是需要逐步推广应用到现存网络当中。</p>
</blockquote>
<p><strong>为什么每台电脑都要设置子网掩码?</strong></p>
<blockquote>
<p>在正规网络环境中,设备,如电脑等,都是连接在交换机上的,交换机再往上接到路由器上。子网掩码用来判断是否在一个网段。<br>如果掩码位相同,则判定为同一局域网,则直接将数据包发给交换机,通过MAC地址表转发就可以通信了。如果掩码位不同,则判定不在同一局域网,需要路由出去,所以就需要把数据包发给网关,由 路由器根据路由表转发出去,进行外网路由通信。<br>所以每台设备上都要配子网掩码,这样设备通信的时候才知道这个数据包要发给路由器还是直接交换机就能处理。</p>
</blockquote>
<p><strong>为什么以太网无法接收大于1500字节的数据包?</strong></p>
<blockquote>
<p>综合考虑信道利用率和信道状态的折中方法,因为帧长太小信道利用率低,而帧长太大,出错重传效率会降低,具体地:<br>1、数据包越大,出错的可能性越大;<br>2、数据包一旦出错就需要重传,大的数据包重传花费的时间多,从而导致网速的下降;<br>3、时延增大,数据包只有在被完整收到的时候才会开始做校验检查,确认收到的每个bit都没有出错,如果frame包太大,将花费较长时间等待数据包进行校验,从而不能将包及时交给上层;<br>4、所有用户共享一个公共节点的资源,每个用户的每个资源切片一定要有所限制,不然其他人就只能干等着。这个切片规定多大要考虑效率问题 。<br>5、最初以太网是在共享介质上面而且是半双工的所有机器共享10Mbps的带宽,如果发送的帧太长,就会占用太久的网络,不仅会影响其他机器的通信,而且会让更多的机器等待到这一帧发送结束之后一齐发送,造成更多的冲突。现在其实已经没有这个限制了,许多交换机都支持更大的MTU,不过单纯提高MTU也不能提高性能。<br>6、交换机的成本和造价。单个数据块越大,交换机就需要越大的缓存。为什么这么说呢?交换机的设计,并不是像应用程序一样,数据包多大就申请多大的缓存。他一般是硬件加速的方法,为每个数据块分配固定长度的缓存。不论你发送的数据块多大,100或1500,交换机都按1500去为每个数据包分配相同大小的缓存。这样的实现有3个好处:一个是快,相同的操作执行速度高;二是内存无碎片,不规则的内存申请释放,最后会留下很多内存碎片,不需要复杂的内存管理;三是防止错误和攻击。如果根据数据内容的“长度”去申请内存,如果长度域出错怎么办,如果有人故意把长度域设置非常大攻击怎么办,如果有人故意把长度域设置小但是数据块却故意设置大怎么办。</p>
</blockquote>
<p><strong>为什么网关与主机可以不在同一个网段?</strong></p>
<blockquote>
<p>要深入理解这个问题,首先要搞清楚网络通讯的原理,网络上通讯工作在物理层和数据链路层,源地址和目标地址是通过源和目的的mac地址进行通讯的。<br>当源主机访问目标主机时,首先看两者的IP在不在同一网段,结果是:<br>1、两者在同一网段,就会直接把包发向目标IP,这时要做:<br>1.1 查本地arp缓存,看看是否有IP和Mac的对应表.<br>1.1.1 有,直接向网络上发包,包中包括原mac及目标mac。<br>1.1.2 没有,则向网络发arp广播,用来查找与目标IP对应的mac地址(ARP发送的是广播数据,电缆上的每个以太网接口都要接收广播的数据帧)。<br>1.1.2.1 如果查到了,则向网络发包。<br>1.1.2.2 没查到,则不通讯。</p>
<p>2、两者不在同一网段,则把目标地址转为网关地址(也就是平时说的向网关发包),然后查找本地arp缓存,继续1.1 。</p>
<p>由此可以看出,源主机和网关的通讯过程中,并不会检查两者是不是同一网段,而是直接去查arp缓存或者发送ARP广播。所以是可能通讯的。</p>
</blockquote>
<p><strong>NAT 和 DHCP 的区别是什么?</strong></p>
<blockquote>
<p>DHCP 用于为网络内的主机分配 IP 地址和一些其他与网络配置有关的信息;NAT 用于在网络间翻译地址,以达到同一个网络内的多个主机(对应多个地址)通过同一个网关(一个地址)来访问外部网络<br>简单来说:<br>DHCP是维护私网(局域网)秩序,而NAT是两个网之间(私网到私网、私网到公网)联系的桥梁,这是区别;<br>DHCP维护的IP可以通过NAT访问不在本网的服务器或其他客户端,这是联系。</p>
</blockquote>
<p><strong>Syn Flood 攻击以及如何防范?</strong></p>
<blockquote>
<p>1 什么是SYN Flood攻击<br>在TCP三次握手时,服务器接收客户端的SYN请求,操作系统将为该请求分配一个TCP(Transmission Control Block),服务器返回一个SYN/ACK请求,并将处于SYN_RCVD状态(半开连接状态)。<br>从以上过程可以看到,如果恶意的向某个服务器端口发送大量的SYN包,则可以使服务器打开大量的半开连接,分配TCB,从而消耗大量的服务器资源,同时也使得正常的连接请求无法被相应。而攻击发起方的资源消耗相比较可忽略不计。<br>SYN Flood是当前最流行的DoS(拒绝服务攻击)与DDoS(分布式拒绝服务攻击)的方式之一。<br>2 怎样发现自己处于被攻击状态<br>(1)服务端无法提供正常的TCP服务。连接请求被拒绝或超时;<br>(2)通过 netstat -an 命令检查系统,发现有大量的SYN_RECV连接状态。 <br>3 防御措施<br>(1)使用TCP Wrapper,服务端只处理有限来源IP的TCP连接请求,其它未指定来源的连接请求一概拒绝。<br>(2)缩短SYN Timeout时间,由于SYN Flood攻击的效果取决于服务器上保持的SYN半连接数,这个值=SYN攻击的频度 x SYN Timeout,所以通过缩短从接收到SYN报文到确定这个报文无效并丢弃改连接的时间,例如设置为20秒以下(过低的SYN Timeout设置可能会影响客户的正常访问),可以成倍的降低服务器的负荷。<br>(3)设置SYN Cookie,就是给每一个请求连接的IP地址分配一个Cookie,如果短时间内连续受到某个IP的重复SYN报文,就认定是受到了攻击,以后从这个IP地址来的包会被一概丢弃。<br>(4)使用SYN Proxy防火墙<br>Syn Cache技术和Syn Cookie技术总的来说是一种主机保护技术,需要系统的TCP/IP协议栈的支持,而目前并非所有的操作系统支持这些技术。因此很多防火墙中都提供一种 SYN代理的功能,其主要原理是对试图穿越的SYN请求进行验证后才放行,</p>
</blockquote>
<p><strong>静态路由和动态路由各自的优缺点?</strong></p>
<blockquote>
<p>1)静态路由是指由网络管理员手工配置的路由信息。当网络的拓扑结构或链路的状态发生变化时,网络管理员需要手工去修改路由表中相关的静态路由信息。<br>2)动态路由是指路由器能够自动地建立自己的路由表,并且能够根据实际实际情况的变化适时地进行调整。</p>
<p>动态路由的优点:<br> 增加或删除网络时,管理员维护路由配置的工作量较少。<br> 网络拓扑结构发生变化时,协议可以自动做出调整。<br> 配置不容易出错。<br> 扩展性好,网络增长时不会出现问题。<br>动态路由的缺点:<br> 需要占用路由器资源(CPU 时间、内存和链路带宽)。<br> 管理员需要掌握更多的网络知识才能进行配置、验证和故障排除工作。</p>
</blockquote>
<p><strong>路由器和交换机的不同之处有哪些?</strong></p>
<blockquote>
<p>1)范围的差别。交换机是用来连接局域网的,路由器是可以跨越局域网的。<br>2)寻址方式。路由器在网络层,路由器根据IP地址寻址。交换机在中继层,交换机根据MAC地址寻址。<br>3)防火墙。路由器提供防火墙的服务,交换机不能提供该功能。</p>
</blockquote>
<p><strong>防火墙的端口防护?</strong></p>
<blockquote>
<p>指通过对防火墙的端口开关的设置,关闭一些非必需端口,达到一定安全防护目的行为.<br>对某一个端口(例如telnet的端口为23)进行基于IP地址,方向,关键字等的检查过滤,判断是否准入或准出,以保护防火墙内的系统。</p>
</blockquote>
<p><strong>Ping命令使用的哪种报文</strong></p>
<blockquote>
<p>Ping利用的就是ICMP ECHO和ICMP ECHO REPLY包来探测主机是否存在,这两个分别使用:ICMP ECHO(Type 8) 和ECHO Reply (Type 0)。</p>
</blockquote>
<p><strong>两台笔记本电脑连起来后ping不通,你觉得可能是哪些问题造成的?</strong></p>
<blockquote>
<p>1 IP是否在同一网段(ip地址配置有问题)<br>2 防火墙是否禁用了PING回复<br>3 网卡是否启用(tcp/ip安装的不完整)<br>4 还有就是网线具体接触是否正常<br>为什么有时ping服务器第一包丢失?</p>
<p>RFC 826号文档中有如下描述,如果没有ARP映射,则地址解析模块将通知调用方(在这里也就是上层的IP)它将会丢弃报文(当然在这里地址解析模块假设的是这个被丢弃的分组将会被高层重传,然而IP并没有数据恢复这个功能)。<br>有时发送ping命令,并没有网关的ARP地址,所以会产生丢包现象。</p>
</blockquote>
<p><strong>ICMP是属于什么协议,处于那一层?</strong></p>
<blockquote>
<p>该协议是TCP/IP协议集中的一个子协议,属于网络层协议。<br>主要用于在主机与路由器之间传递控制信息,包括报告错误、交换受限控制和状态信息等。<br>当遇到IP数据无法访问目标、IP路由器无法按当前的传输速率转发数据包等情况时,会自动发送ICMP消息。<br>我们可以通过Ping命令发送ICMP回应请求消息并记录收到ICMP回应回复消息,通过这些消息来对网络或主机的故障提供参考依据。</p>
<p>ICMP包头主要包括:TYPE类型,CODE代码和校验和。</p>
</blockquote>
<h2 id="数据链路层"><a href="#数据链路层" class="headerlink" title="数据链路层"></a>数据链路层</h2><p><strong>数据链路层的CSMA/CD协议?</strong></p>
<blockquote>
<p>本质原因:在传统的共享以太网中,所有的节点共享传输介质。<br>CSMA/CD(Carrier Sense Multiple Access with Collision Detection)即带冲突检测的载波监听多路访问技术(载波监听多点接入/碰撞检测)。<br>如何保证传输介质有序、高效地为许多节点提供传输服务,就是以太网的介质访问控制协议要解决的问题。</p>
<p>过程:先听后发,边发边听,冲突停发,随机延迟后重发。<br>它的工作原理是:发送数据前先侦听信道是否空闲,若空闲,则立即发送数据。若信道忙碌,则等待一段时间至信道中的信息传输结束后再发送数据;若在上一段信息发送结束后,同时有两个或两个以上的节点都提出发送请求,则判定为冲突。若侦听到冲突,则立即停止发送数据,等待一段随机时间,再重新尝试。</p>
</blockquote>
<p><strong>网桥的作用?</strong></p>
<blockquote>
<p>网桥是一个局域网与另一个局域网之间建立连接的桥梁.<br>网桥工作在数据链路层,将两个LAN(局域网)连起来,根据MAC地址来转发帧,可以看作一个“低层的路由器”(路由器工作在网络层,根据网络地址如IP地址进行转发)。 </p>
<p>网桥的功能在延长网络跨度上类似于中继器,然而它能提供智能化连接服务,即根据帧的终点地址处于哪一网段来进行转发和滤除。网桥对站点所处网段的了解是靠“自学习”实现的。<br>使用网桥进行互连克服了物理限制,这意味着构成LAN的数据站总数和网段数很容易扩充。</p>
<p>网桥的中继功能仅仅依赖于MAC帧的地址,因而对高层协议完全透明。<br>网桥将一个较大的LAN分成段,有利于改善可靠性、可用性和安全性。</p>
</blockquote>
<h2 id="网络编程"><a href="#网络编程" class="headerlink" title="网络编程"></a>网络编程</h2><p><strong>编写socket套接字的步骤?</strong></p>
<blockquote>
<p>基于TCP的socket通信模型<br>服务器:<br>1.创建套接字描述符(socket)<br>2.设置服务器的IP地址和端口号(需要转换为网络字节序的格式)<br>3.将套接字描述符绑定到服务器地址(bind)<br>4.将套接字描述符设置为监听套接字描述符(listen),等待来自客户端的连接请求,监听套接字维护未完成连接队列和已完成连接队列<br>5.从已完成连接队列中取得队首项,返回新的已连接套接字描述符(accept),如果已完成连接队列为空,则会阻塞<br>6.从已连接套接字描述符读取来自客户端的请求(read)<br>7.向已连接套接字描述符写入应答(write)<br>8.关闭已连接套接字描述符(close),回到第5步等待下一个客户端的连接请求<br>客户端:<br>1.创建套接字描述符(socket)<br>2.设置服务器的IP地址和端口号(需要转换为网络字节序的格式)<br>3.请求建立到服务器的TCP连接并阻塞,直到连接成功建立(connect)<br>4.向套接字描述符写入请求(write)<br>5.从套接字描述符读取来自服务器的应答(read)<br>6.关闭套接字描述符(close)</p>
<p>基于UDP套接字编程<br>服务器:<br>1.创建套接字描述符(socket)<br>2.设置服务器的IP地址和端口号(需要转换为网络字节序的格式)<br>3.将套接字描述符绑定到服务器地址(bind)<br>4.从套接字描述符读取来自客户端的请求并取得客户端的地址(recvfrom)<br>5.向套接字描述符写入应答并发送给客户端(sendto)<br>6.回到第4步等待读取下一个来自客户端的请求<br>客户端:<br>1.创建套接字描述符(socket)<br>2.设置服务器的IP地址和端口号(需要转换为网络字节序的格式)<br>3.向套接字描述符写入请求并发送给服务器(sendto)<br>4.从套接字描述符读取来自服务器的应答(recvfrom)<br>5.关闭套接字描述符(close)</p>
</blockquote>
<p><strong>什么是网络套接字(Socket)?流套接字(SOCK_STREAM)基于什么协议?</strong></p>
<blockquote>
<p>网络套接字是一个5元组,<源IP, 源端口,目的IP, 目的端口,协议>。只要这个5元组中有任何一个元素不相同个就认为是不同的部分,其中协议常见的是TCP或者是UDP协议。<br>流套接字是基于TCP协议的,数据报套接字是基于UDP协议的。还有一种是raw socket,是可以自己进行协议包装的,比如说syn flood中ip地址伪造就是用的raw socket进行的。</p>
</blockquote>
<p><strong>Linux/Unix Socket编程并发时什么时候用进程(fork),什么时候用线程(池)?</strong></p>
<blockquote>
<p>答案一:<br>1)进程:子进程是父进程的复制品。子进程获得父进程数据空间、堆和栈的复制品。<br>2)线程:相对与进程而言,线程是一个更加接近与执行体的概念,它可以与同进程的其他线程共享数据,但拥有自己的栈空间,拥有独立的执行序列。<br>两者都可以提高程序的并发度,提高程序运行效率和响应时间。<br>线程和进程在使用上各有优缺点:线程执行开销小,但不利于资源管理和保护;而进程正相反。同时,线程适合于在SMP(Symmetric Multi-Processing,对称多处理结构的简称,是指在一个计算机上汇集了一组处理器(多CPU),各CPU之间共享内存子系统以及总线结构。)机器上运行,而进程则可以跨机器迁移。<br>答案二:<br>根本区别就一点:用多进程每个进程有自己的地址空间(address space),线程则共享地址空间。所有其它区别都是由此而来的:<br>1)速度:线程产生的速度快,线程间的通讯快、切换快等,因为他们在同一个地址空间内。<br>2)资源利用率:线程的资源利用率比较好也是因为他们在同一个地址空间内。<br>3)同步问题:线程使用公共变量/内存时需要使用同步机制还是因为他们在同一个地址空间内。</p>
</blockquote>
<p><strong>对一个已经关闭了的 socket 的 Server 调用 write 操作?</strong></p>
<blockquote>
<p>当服务器close一个连接时,若client端接着发数据。根据TCP协议的规定,会收到一个RST响应,client再往这个服务器发送数据时,系统会发出一个SIGPIPE信号给进程,告诉进程这个连接已经断开了,不要再写了。<br>根据信号的默认处理规则SIGPIPE信号的默认执行动作是terminate(终止、退出),所以client会退出。若不想客户端退出可以把SIGPIPE设为SIG_IGN 如: signal(SIGPIPE,SIG_IGN); 这时SIGPIPE交给了系统处理。</p>
</blockquote>
<p><strong>怎样实时判断socket链接状态?</strong></p>
<blockquote>
<p>对端正常close socket,或者进程退出(正常退出或崩溃),对端系统正常关闭这种情况下,协议栈会走正常的关闭状态转移,使用epoll的话,一般要判断如下几个情况:<br>1、处理可读事件时,在循环read后,返回结果为0。<br>2、处理可写事件时,write返回-1,errno为EPIPE。<br>3、EPOLLERR或EPOLLHUP或事件。<br>对端非正常断开,比如服务器断电,网线被拔掉这种情况下,协议栈无法感知,SO_KEEPALIVE这个选项的超时事件太长并不实用,一般还是以应用层的heartbeat来及时发现。</p>
</blockquote>
<p><strong>socket套接字在多线程发送数据时要加锁吗?</strong></p>
<blockquote>
<p>socket是全双工的, 多线程同时 send 和 recv 没问题。<br>在操作系统层面, send 和 recv 均是原子操作, 多线程对同一个socket 发送数据, 无需加锁。</p>
</blockquote>
<p><strong>同步IO和异步IO的区别?</strong></p>
<blockquote>
<p>同步IO:如果有数据就返回,没有数据就等待,直至数据到来这种模式。网络编程中的read函数就是同步IO。<br>异步IO:你只管调用,调用完走就可以了,操作系统会帮你把数据处理好,然后在数据处理好之后会通知你,这就是异步IO。Linux系统中的aio_read函数就是异步IO函数。</p>
</blockquote>
<p>同步,异步,阻塞,非阻塞辨析?</p>
<blockquote>
<p>阻塞:等待<br>非阻塞:不等待,立即返回,但是多次轮询<br>这二者是针对I/O模型而言的。</p>
<p>而同步、异步是针对应用程序与内核的交互而言的。<br>同步:进程触发IO操作并等待或者轮询的去查看IO操作是否完成。<br>异步:进程触发IO操作以后,直接返回,做自己的事情,IO交给内核来处理,完成后内核通知进程IO完成。</p>
</blockquote>
<p><strong>什么是IOCP?</strong></p>
<blockquote>
<p>IOCP全称I/O Completion Port, 是window上的一种高效的异步通信模型,是真正的异步通信模型。<br>提到IOCP就要提到epoll了。<br>以epoll为核心的reactor模型和以IOCP为核心的proactor模型,是两种不同的通讯模型。<br>1)Reactor核心就是监听事件的发生,如果事件发生会通知你进行后续的处理.<br>2)而Proactor模型的核心是,在你监听事件的同时,告诉它应该怎么处理,比如说读数据应该读取到哪里,这就需要你提前开辟好内存空间,然后当事件结束完成以后,会通知你,这个时候你就可以直接操作数据了,因为内核已经将数据从内核空间拷贝到了用户态空间。<br>ASIO网络框架就是Proactor模型。</p>
</blockquote>
<p><strong>什么是SO_LINGER选项?</strong></p>
<blockquote>
<p>异常关闭的实现是通过setsockopt()函数中的SO_LINGER选项实现的。前面我们提到在正常情况下主动关闭连接会把发送缓冲区中的数据都发送出去,但是并不知道对端的TCP是否确认了数据。</p>
<p>而使用SO_LINGER选项,并将l_linger的值设置为大于0,那么就会在调用close()之后,close()不立即返回,而是等待l_linger秒,直到对端发送了对数据和FIN的确认之后,close()再成功返回,</p>
<p>还有一种close()返回的情况,那就是过了l_linger秒之后,对端还没有发送对数据和FIN的确认,这时close()会返回-1并设置errno为EWOULDBLOCK。这里有很重要的一点就是,即使close成功返回了,但是并不代表对端应用程序是否已读取数据,只能说明对端确认了数据和FIN。<br>如果l_linger的值设置为0,那么就会造成上面我们进行实验的情况,立即关闭连接,跳过了TIME_WAIT状态。 </p>
<p>SO_LINGER还有一个作用就是用来减少TIME_WAIT套接字的数量。在设置SO_LINGER选项时,指定等待时间为0,此时调用主动关闭时不会发送FIN来结束连接,而是直接将连接设置为CLOSE状态,清除套接字中的发送和接收缓冲区,直接对对端发送RST包。</p>
</blockquote>
<p><strong>什么是SO_REUSEADDR选项?</strong></p>
<blockquote>
<p>(1)SO_REUSEADDR允许启动一个监听服务器并捆绑其众所周知的端口,即使以前建立的将该端口用作他们的本地端口的连接仍存在。<br>(2)允许在同一端口上启动同一服务器的多个实例,只要每个实例捆绑一个不同的本地IP地址即可。<br>(3)SO_REUSEADDR 允许单个进程捆绑同一端口到多个套接字上,只要每次捆绑指定不同的本地IP地址即可。<br>(4)SO_REUSEADDR允许完全重复的捆绑:当一个IP地址和端口号已绑定到某个套接字上时,如果传输协议支持,同样的IP地址和端口还可以捆绑到另一个套接字上。一般来说本特性仅支持UDP套接字。</p>
</blockquote>
<p><strong>五种IO模型?</strong></p>
<p><img src="https://blog-image-1256228880.cos.ap-beijing.myqcloud.com/5io.png" alt=""></p>
<p>select,poll,epoll的区别?</p>
<blockquote>
<p>select,poll,epoll的区别<br>1/ <strong>select</strong>:是最初解决IO阻塞问题的方法。用结构体fd_set来告诉内核监听多个文件描述符,该结构体被称为描述符集。由数组来维持哪些描述符被置位了。对结构体的操作封装在三个宏定义中。通过轮寻来查找是否有描述符要被处理,如果没有返回<br>存在的问题:</p>
<ol>
<li>内置数组的形式使得select的最大文件数受限与FD_SIZE;</li>
<li>每次调用select前都要重新初始化描述符集,将fd从用户态拷贝到内核态,每次调用select后,都需要将fd从内核态拷贝到用户态;</li>
<li>轮寻排查当文件描述符个数很多时,效率很低;<br>2/ <strong>poll</strong>:通过一个可变长度的数组解决了select文件描述符受限的问题。数组中元素是结构体,该结构体保存描述符的信息,每增加一个文件描述符就向数组中加入一个结构体,结构体只需要拷贝一次到内核态。poll解决了select重复初始化的问题。轮寻排查的问题未解决。<br>3/ <strong>epoll</strong>:轮寻排查所有文件描述符的效率不高,使服务器并发能力受限。因此,epoll采用只返回状态发生变化的文件描述符,便解决了轮寻的瓶颈。</li>
</ol>
<p>在消息传递方式上,select,poll,epoll 具有以下区别:<br>1)select, poll: 内核需要将消息传递到用户空间,都需要内核拷贝动作<br>2)通过内核和用户空间共享一块内存(事件表)来实现。</p>
<p>综上,在选择select,poll,epoll时要根据具体的使用场合以及这三种方式的自身特点:<br>(1)表面上看epoll的性能最好,但是在连接数少并且连接都十分活跃的情况下,select和poll的性能可能比epoll好,毕竟epoll的通知机制需要很多函数回调。<br>(2)select低效是因为每次它都需要轮询。但低效也是相对的,视情况而定,也可通过良好的设计改善。</p>
<p>更通俗的说法:<br>从内核源码来分析吧,诶不用介绍select吗…算了,其实select跟poll是差不多的,复用了很多代码,只是记录监听events的数据结构不一样…(先介绍了select,然后讲了一下与poll的区别)。epoll的话,在类unix系统中好像只有linux有,epoll把epoll实例创建、events增删改还有events轮询都分开了,这样的话epoll实例就可以被同一个进程中的所有线程共享。epoll跟poll一样,使用链表节点记录监听events,但是呢它有三个链表型结构(<strong>就绪链表、辅助链表、红黑树</strong>),首先想要监听的events的节点被放到红黑树里,这样可以加快events节点的访问。events就绪之后会被挂载到就绪链表里去,当epoll_wait从内核空间向用户空间写出就绪events的时候,会遍历就绪链表,同时这个时候可能还会发生新的就绪events,这个时候已就绪的events不再添加到就绪链表里去,而是使用辅助链表…</p>
</blockquote>
<p>好文章:[linux下非阻塞io库 epoll](<a href="https://www.jianshu.com/p/b5bc204da984" target="_blank" rel="external">https://www.jianshu.com/p/b5bc204da984</a></p>
<p><strong>tcp 阻塞时,socket的 send recv需要注意的操作?</strong></p>
<blockquote>
<p>在阻塞条件下,read/recv/msgrcv的行为::<br> 1、如果没有发现数据在网络缓冲中会一直等待,<br> 2、当发现有数据的时候会把数据读到用户指定的缓冲区,但是如果这个时候读到的数据量比较少,比参数中指定的长度要小,read 并不会一直等待下去,而是立刻返回。<br> read 的原则::是数据在不超过指定的长度的时候有多少读多少,没有数据就会一直等待。<br> 所以一般情况下::我们读取数据都需要采用循环读的方式读取数据,因为一次read 完毕不能保证读到我们需要长度的数据,read 完一次需要判断读到的数据长度再决定是否还需要再次读取。</p>
<p>阻塞情况下: 阻塞情况下,write会将数据发送完。(不过可能被中断)<br>在阻塞的情况下,是会一直等待,直到write 完,全部的数据再返回.这点行为上与读操作有所不同。<br>原因: 读,究其原因主要是读数据的时候我们并不知道对端到底有没有数据,数据是在什么时候结束发送的,如果一直等待就可能会造成死循环,所以并没有去进行这方面的处理;写,而对于write, 由于需要写的长度是已知的,所以可以一直再写,直到写完.不过问题是write 是可能被打断吗,造成write 一次只write 一部分数据, 所以write 的过程还是需要考虑循环write, 只不过多数情况下一次write 调用就可能成功.</p>
</blockquote>
<p><strong>怎样理解阻塞非阻塞与同步异步的区别?</strong></p>
<blockquote>
<p>1)同步与异步<br>同步和异步关注的是消息通信机制 (synchronous communication/ asynchronous communication)<br>所谓同步,就是在发出一个<em>调用</em>时,在没有得到结果之前,该<em>调用</em>就不返回。但是一旦调用返回,就得到返回值了。<br>而异步则是相反,<em>调用</em>在发出之后,这个调用就直接返回了,所以没有返回结果。换句话说,当一个异步过程调用发出后,调用者不会立刻得到结果。而是在<em>调用</em>发出后,<em>被调用者</em>通过状态、通知来通知调用者,或通过回调函数处理这个调用。<br>2)阻塞与非阻塞<br>阻塞和非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态.<br>阻塞调用是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会返回。<br>非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程。</p>
<table>
<thead>
<tr>
<th style="text-align:center">IO 模式</th>
<th style="text-align:center">阻塞</th>
<th style="text-align:center">非阻塞</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:center">同步</td>
<td style="text-align:center">BIO:等待,阻塞,MPIO:多路复用</td>
<td style="text-align:center">NBIO: 轮询</td>
</tr>
<tr>
<td style="text-align:center">异步</td>
<td style="text-align:center"></td>
<td style="text-align:center">AIO:异步,回调</td>
</tr>
</tbody>
</table>
</blockquote>
<p><strong>epoll:EPOLLLT和EPOLLET的区别?</strong></p>
<blockquote>
<p>Level-Triggered :水平触发,缺省模式。<br>Edge-Triggered :边缘触发。<br>通知模式:<br>LT模式时,事件就绪时,假设对事件没做处理,内核会反复通知事件就绪<br>ET模式时,事件就绪时,假设对事件没做处理,内核不会反复通知事件就绪(边沿只会出现一次)</p>
<p>适用场景:<br>LT模式比较慢,但是比较安全,也就是如果真的是就绪的话它会再次通知你;<br>ET模式比较快,但是有可能造成事件的丢失,这就可能让程序永远阻塞。LT为了担责,降低了效率,而ET为了效率将责任推给了用户</p>
</blockquote>
<p><strong>Linux-socket的close和shutdown区别及应用场景?</strong></p>
<blockquote>
<p>shutdown可以只关闭读或者只关闭写,close关闭读写。<br>多进程<br>1.如果有多个进程共享一个套接字,close每被调用一次,计数减1,直到计数为0时,也就是所用进程都调用了close,套接字将被释放。<br>2.在多进程中如果一个进程中shutdown(sfd, SHUT_RDWR)后其它的进程将无法进行通信。如果一个进程close(sfd)将不会影响到其它进程,得自己理解引用计数的用法了。</p>
<p>使用场景:<br>有些时候,你会想在socket上实现单向的socket,即数据往一个方向传输。单向的socket便称为半开放Socket。要实现半开放式,需要用到shutdown()函数。<br>一般来说,半开放socket适用于以下场合:<br>1.当你想要确保所有写好的数据已经发送成功时。如果在发送数据的过程中,网络意外断开或者出现异常,系统不一定会返回异常,这是你可能以为对端已经接收到数据了。这时需要用shutdown()来确定数据是否发送成功,因为调用shutdown()时只有在缓存中的数据全部发送成功后才会返回。<br>2.想用一种方法来捕获程序潜在的错误,这错误可能是因为往一个不能写的socket上写数据,也有可能是在一个不该读操作的socket上读数据。当程序尝试这样做时,将会捕获到一个异常,捕获异常对于程序排错来说是相对简单和省劲的。</p>
</blockquote>
<p><strong>reactor和proactor的区别?</strong></p>
<blockquote>
<p>一般地,I/O多路复用机制都依赖于一个事件多路分离器(Event Demultiplexer)。分离器对象可将来自事件源的I/O事件分离出来,并分发到对应的read/write事件处理器(Event Handler)。开发人员预先注册需要处理的事件及其事件处理器(或回调函数);事件分离器负责将请求事件传递给事件处理器。两个与事件分离器有关的模式是Reactor和Proactor。Reactor模式采用同步IO,而Proactor采用异步IO。<br>1)在Reactor中,事件分离器负责等待文件描述符或socket为读写操作准备就绪,然后将就绪事件传递给对应的处理器,最后由处理器负责完成实际的读写工作。相当于具体I/O操作还是工作线程来干。<br>2)而在Proactor模式中,处理器或者兼任处理器的事件分离器,只负责发起异步读写操作。IO操作本身由操作系统来完成。传递给操作系统的参数需要包括用户定义的数据缓冲区地址和数据大小,操作系统才能从中得到写出操作所需数据,或写入从socket读到的数据。相当于所有I/O操作都交给主线程和内核,工作线程只负责业务逻辑,落得轻松。</p>
</blockquote>
<p><strong>如何测量网络发送速度?</strong></p>
<blockquote>
<p>1)ping命令可以通过向目标网站发送数据包,从数据包的平均达到时间和丢包率来判断,从本地到目标网站的网络情况。<br>2)tracert命令是使用从本地到目标网站所在网络服务器的一系列网络节点的访问速度,网络节点最多支持显示30个。</p>
</blockquote>
<p><strong>如果将同一个listening socket加入多个epoll, 是不是一种合理的设计?</strong></p>
<blockquote>
<p>并不是很合理的设计,首先,一般来说,一个epoll单属于一个线程,多个线程中每个线程拥有一个epoll。考虑一种情况,多个epoll中都存在同一个监听套接字,那么当监听套接字连接队列不为空,原本多个线程阻塞在epoll_wait,现在都被唤醒,但是实际上,仅仅一个线程才能从accept返回,其他的线程则会继续被阻塞。所有线程的唤醒和阻塞,导致了无用的开销。针对这种惊群现象,可以考虑nginx的做法,具体见链接。Nginx中利用锁解决惊群现象的做法。</p>
</blockquote>
<p><strong>IP首部、TCP首部、UDP首部、以太网首部?</strong></p>
<p>下面是IP首部报文格式图:20字节<br><img src="https://blog-image-1256228880.cos.ap-beijing.myqcloud.com/iphead.jpg" alt=""><br>1、第一个4字节(也就是第一行):<br>(1)版本号(Version),4位;用于标识IP协议版本,IPv4是0100,IPv6是0110,也就是二进制的4和6。<br>(2)首部长度(Internet Header Length),4位;用于标识首部的长度,单位为4字节,所以首部长度最大值为:(2^4 - 1) * 4 = 60字节,但一般只推荐使用20字节的固定长度。<br>(3)服务类型(Type Of Service),8位;用于标识IP包的优先级,但现在并未使用。<br>(4)总长度(Total Length),16位;标识IP数据报的总长度,最大为:2^16 -1 = 65535字节。<br>2、第二个四字节:<br>(1)标识(Identification),16位;用于标识IP数据报,如果因为数据链路层帧数据段长度限制(也就是MTU,支持的最大传输单元),IP数据报需要进行分片发送,则每个分片的IP数据报标识都是一致的。<br>(2)标志(Flag),3位,但目前只有2位有意义;最低位为MF,MF=1代表后面还有分片的数据报,MF=0代表当前数据报已是最后的数据报。次低位为DF,DF=1代表不能分片,DF=0代表可以分片。<br>(3)片偏移(Fragment Offset),13位;代表某个分片在原始数据中的相对位置。<br>3、第三个四字节:<br>(1)生存时间(TTL),8位;以前代表IP数据报最大的生存时间,现在标识IP数据报可以经过的路由器数。<br>(2)协议(Protocol),8位;代表上层传输层协议的类型,1代表ICMP,2代表IGMP,6代表TCP,17代表UDP。<br>(3)校验和(Header Checksum),16位;用于验证数据完整性,计算方法为,首先将校验和位置零,然后将每16位二进制反码求和即为校验和,最后写入校验和位置。<br>4、第四个四字节:源IP地址<br>5、第五个四字节:目的IP地址</p>
<p>下面是TCP首部报文格式图:20个字节<br><img src="https://blog-image-1256228880.cos.ap-beijing.myqcloud.com/tcphead.png" alt=""><br>1、第一个4字节:<br>(1)源端口,16位;发送数据的源进程端口<br>(2)目的端口,16位;接收数据的进程端口<br>2、第二个4字节与第三个4字节<br>(1)序号,32位;代表当前TCP数据段第一个字节占整个字节流的相对位置;<br>(2)确认号,32位;代表接收端希望接收的数据序号,为上次接收到数据报的序号+1,当ACK标志位为1时才生效。<br>3、第四个4字节:<br>(1)数据偏移,4位;实际代表TCP首部长度,最大为60字节。<br>(2)6个标志位,每个标志位1位;<br>SYN,为同步标志,用于数据同步;<br>ACK,为确认序号,ACK=1时确认号才有效;<br>FIN,为结束序号,用于发送端提出断开连接;<br>URG,为紧急序号,URG=1是紧急指针有效;<br>PSH,指示接收方立即将数据提交给应用层,而不是等待缓冲区满;<br>RST,重置连接。<br>(3)窗口值,16位;标识接收方可接受的数据字节数。详解可参看:<br>4、第五个4字节<br>(1)校验和,16位;用于检验数据完整性。<br>(2)紧急指针,16位;只有当URG标识位为1时,紧急指针才有效。紧急指针的值与序号的相加值为紧急数据的最后一个字节位置。用于发送紧急数据。</p>
<p>下面是UDP首部报文格式图:8字节<br><img src="https://blog-image-1256228880.cos.ap-beijing.myqcloud.com/udphead.png" alt=""></p>
<ol>
<li>源端口:源端口号。在需要对方回信时选用,不需要时可用全0. </li>
<li>目的端口:目的端口号。这在终点交付报文时必须要使用到。 </li>
<li>长度: UDP用户数据报的长度,其最小值是8(仅有首部)。 </li>
<li>检验和:检测UDP用户数据报在传输中是否有错。有错就丢弃</li>
</ol>
<p>如有误,欢迎批评指正!</p>
<p>通常现在的计算机网络都采用一种分层模型来构建,本文也以分层的结构来梳理计算机网络常见面试问题。</p>
<h2 id="层次结构总述"><a href="#层次结构总述" class="headerlink" title="层次结构总述"></a>层次结构总述</h2><p
GPU之更新CUDA及驱动版本
http://whatbeg.com/2019/06/05/gpudriverupdate.html
2019-06-05T03:14:08.000Z
2019-06-05T03:46:59.586Z
<p>CUDA版本迭代很快,没多久就到10.0以上啦,有些应用需要用到高版本的CUDA,于是之前的CUDA-8.0得退役了。</p>
<p>CUDA的更新往往也伴随着NVIDIA驱动的更新,因此,在更新CUDA前,有可能还需要更新驱动版本。</p>
<p>本文讲述了将CUDA 8.0升级到10.0并重装驱动的过程。</p>
<p>CUDA及其驱动兼容情况如下表所示。具体可参见:<a href="https://docs.nvidia.com/cuda/cuda-toolkit-release-notes/index.html" target="_blank" rel="external">https://docs.nvidia.com/cuda/cuda-toolkit-release-notes/index.html</a></p>
<p>Table 1. CUDA Toolkit and Compatible Driver Versions</p>
<table>
<thead>
<tr>
<th style="text-align:center">CUDA Toolkit</th>
<th style="text-align:center"><code>Linux x86_64 Driver Version</code></th>
<th style="text-align:center"><code>Windows x86_64 Driver Version</code></th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:center">CUDA 10.1.105</td>
<td style="text-align:center"><code>>= 418.39</code></td>
<td style="text-align:center"><code>>= 418.96</code></td>
</tr>
<tr>
<td style="text-align:center">CUDA 10.0.130</td>
<td style="text-align:center"><code>>= 410.48</code></td>
<td style="text-align:center"><code>>= 411.31</code></td>
</tr>
<tr>
<td style="text-align:center">CUDA 9.2 (9.2.148 Update 1)</td>
<td style="text-align:center"><code>>= 396.37</code></td>
<td style="text-align:center"><code>>= 398.26</code></td>
</tr>
<tr>
<td style="text-align:center">CUDA 9.2 (9.2.88)</td>
<td style="text-align:center"><code>>= 396.26</code></td>
<td style="text-align:center"><code>>= 397.44</code></td>
</tr>
<tr>
<td style="text-align:center">CUDA 9.1 (9.1.85)</td>
<td style="text-align:center"><code>>= 390.46</code></td>
<td style="text-align:center"><code>>= 391.29</code></td>
</tr>
<tr>
<td style="text-align:center">CUDA 9.0 (9.0.76)</td>
<td style="text-align:center"><code>>= 384.81</code></td>
<td style="text-align:center"><code>>= 385.54</code></td>
</tr>
<tr>
<td style="text-align:center">CUDA 8.0 (8.0.61 GA2)</td>
<td style="text-align:center"><code>>= 375.26</code></td>
<td style="text-align:center"><code>>= 376.51</code></td>
</tr>
<tr>
<td style="text-align:center">CUDA 8.0 (8.0.44)</td>
<td style="text-align:center"><code>>= 367.48</code></td>
<td style="text-align:center"><code>>= 369.30</code></td>
</tr>
<tr>
<td style="text-align:center">CUDA 7.5 (7.5.16)</td>
<td style="text-align:center"><code>>= 352.31</code></td>
<td style="text-align:center"><code>>= 353.66</code></td>
</tr>
<tr>
<td style="text-align:center">CUDA 7.0 (7.0.28)</td>
<td style="text-align:center"><code>>= 346.46</code></td>
<td style="text-align:center"><code>>= 347.62</code></td>
</tr>
</tbody>
</table>
<h3 id="卸载-CUDA-8-0-Toolkit"><a href="#卸载-CUDA-8-0-Toolkit" class="headerlink" title="卸载 CUDA-8.0 Toolkit"></a>卸载 CUDA-8.0 Toolkit</h3><p>首先将老的 CUDA-8.0 卸载掉,<br><figure class="highlight stata"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">cd</span> /usr/<span class="keyword">local</span>/cuda-8.0/bin</span><br><span class="line">sudo ./uninstall_cuda_toolkit_8.0.<span class="keyword">pl</span></span><br></pre></td></tr></table></figure></p>
<h3 id="卸载老的-NVIDIA-驱动"><a href="#卸载老的-NVIDIA-驱动" class="headerlink" title="卸载老的 NVIDIA 驱动"></a>卸载老的 NVIDIA 驱动</h3><p>本机的NVIDIA驱动为390.12,但是这个run文件找不到了,于是去官网找了一个<code>NVIDIA-Linux-x86_64-390.116.run</code>,运行:<br><figure class="highlight applescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ sh NVIDIA-Linux-x86_64<span class="number">-390.116</span>.<span class="built_in">run</span> <span class="comment">--uninstall</span></span><br></pre></td></tr></table></figure></p>
<p>卸载旧版本 NVIDIA 驱动。</p>
<h3 id="安装新版本-NVIDIA-驱动"><a href="#安装新版本-NVIDIA-驱动" class="headerlink" title="安装新版本 NVIDIA 驱动"></a>安装新版本 NVIDIA 驱动</h3><p>驱动下载地址:<a href="https://www.nvidia.cn/Download/index.aspx?lang=cn" target="_blank" rel="external">https://www.nvidia.cn/Download/index.aspx?lang=cn</a><br>运行<br><figure class="highlight stata"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ <span class="keyword">sh</span> NVIDIA-Linux-x86_64-410.104.<span class="keyword">run</span></span><br></pre></td></tr></table></figure></p>
<p>即可。</p>
<h3 id="安装-CUDA-10-0"><a href="#安装-CUDA-10-0" class="headerlink" title="安装 CUDA-10.0"></a>安装 CUDA-10.0</h3><p>同安装 CUDA-8.0 一致,见<a href="http://whatbeg.com/2018/03/17/cudainstall.html">《CentOS 7 卸载CUDA 9.1 安装CUDA8.0 并安装Tensorflow GPU版》</a></p>
<p>是不是 So Easy~</p>
<p>CUDA版本迭代很快,没多久就到10.0以上啦,有些应用需要用到高版本的CUDA,于是之前的CUDA-8.0得退役了。</p>
<p>CUDA的更新往往也伴随着NVIDIA驱动的更新,因此,在更新CUDA前,有可能还需要更新驱动版本。</p>
<p>本文讲述了将CUDA 8
SQL Server 异常集锦
http://whatbeg.com/2019/06/05/sqlserverexception.html
2019-06-05T03:13:19.000Z
2019-06-05T03:47:18.123Z
<p>本文记录SQL Server及SSMS,SSIS,SSDT等软件使用过程中的一些异常,以备后用。</p>
<h5 id="未在本地计算机上注册“Microsoft-ACE-OLEDB-16-0”提供程序。-System-Data"><a href="#未在本地计算机上注册“Microsoft-ACE-OLEDB-16-0”提供程序。-System-Data" class="headerlink" title="未在本地计算机上注册“Microsoft.ACE.OLEDB.16.0”提供程序。 (System.Data)"></a><strong>未在本地计算机上注册“Microsoft.ACE.OLEDB.16.0”提供程序。 (System.Data)</strong></h5><p>如果对数据库右键->ImportData,可能调用的是<code>SQL Server 20xx Import and Export Data (32bit)</code>,而如果你的机器是64bit,那就会出现此错误。</p>
<p>【解决方案】开始菜单打开<code>SQL Server 20xx Import and Export Data (64bit)</code>进行导入。<br>或者改变ImportData默认调用为64bit程序。</p>
<h5 id="表显示在SSMS中,但select时有红色波浪线,表示Invalid-Object-name"><a href="#表显示在SSMS中,但select时有红色波浪线,表示Invalid-Object-name" class="headerlink" title="表显示在SSMS中,但select时有红色波浪线,表示Invalid Object name"></a><strong>表显示在SSMS中,但select时有红色波浪线,表示Invalid Object name</strong></h5><p>关闭智能感知后不会有红色波浪线警告。<br>一般是智能感知提示缓存没有刷新的原因。</p>
<p>【解决方案】<br>编辑 - > IntelliSense - > 刷新本地缓存</p>
<h5 id="The-project-could-not-be-deployed-to-the-‘localhost’-server"><a href="#The-project-could-not-be-deployed-to-the-‘localhost’-server" class="headerlink" title="The project could not be deployed to the ‘localhost’ server"></a><strong>The project could not be deployed to the ‘localhost’ server</strong></h5><p>SSAS部署(Deployment)时出现:<br><figure class="highlight applescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">Severity Code Description Project File Line Suppression State</span><br><span class="line">Error The project could <span class="keyword">not</span> be deployed <span class="keyword">to</span> <span class="keyword">the</span> 'localhost' server because <span class="keyword">of</span> <span class="keyword">the</span> following connectivity problems :</span><br><span class="line">A connection cannot be made. Ensure <span class="keyword">that</span> <span class="keyword">the</span> server <span class="keyword">is</span> <span class="built_in">running</span>.</span><br><span class="line">To verify <span class="keyword">or</span> update <span class="keyword">the</span> <span class="built_in">name</span> <span class="keyword">of</span> <span class="keyword">the</span> target server, right-click <span class="keyword">on</span> <span class="keyword">the</span> project <span class="keyword">in</span> Solution Explorer, select Project Properties, click <span class="keyword">on</span> <span class="keyword">the</span> Deployment <span class="literal">tab</span>, <span class="keyword">and</span> <span class="keyword">then</span> enter <span class="keyword">the</span> <span class="built_in">name</span> <span class="keyword">of</span> <span class="keyword">the</span> server.</span><br></pre></td></tr></table></figure></p>
<p>【解决方案】<br>可在<code>service.msc</code>控制台查看Analysis Services有无启动,如未启动将其启动。<br>如果控制台没有Analysis Services,那可能是没有安装AS,打开SQL Server Installation Center, 点击Installation-> New SQL Server stand-alone installation or add features to an existing installazion,添加AS。</p>
<h5 id="Deploy时报:出现以下系统错误-用户名或密码不正确"><a href="#Deploy时报:出现以下系统错误-用户名或密码不正确" class="headerlink" title="Deploy时报:出现以下系统错误: 用户名或密码不正确"></a><strong>Deploy时报:出现以下系统错误: 用户名或密码不正确</strong></h5><figure class="highlight crmsh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">Severity Code Description Project File Line Suppression State</span><br><span class="line">Error 高级关系引擎中存在错误。 无法与 DataSourceID 为“<span class="keyword">ORDER</span> <span class="title">DATABASE</span>”、名称为“<span class="keyword">ORDER</span> <span class="title">DATABASE</span>”的数据源建立连接。 <span class="number">0</span> </span><br><span class="line">Error 出现以下系统错误: 用户名或密码不正确。 <span class="number">0</span></span><br></pre></td></tr></table></figure>
<p>双击数据源文件(xx.ds),修改Impersonation Infomation,选择最上面,填入Windows用户名和密码。不要填Windows没有的账号。</p>
<p>本文记录SQL Server及SSMS,SSIS,SSDT等软件使用过程中的一些异常,以备后用。</p>
<h5 id="未在本地计算机上注册“Microsoft-ACE-OLEDB-16-0”提供程序。-System-Data"><a href="#未在本地计算机上注册“
Hadoop必知必会(HDFS+MapReduce部分)
http://whatbeg.com/2019/04/16/hadoopmustknow.html
2019-04-16T13:08:37.000Z
2019-04-16T13:10:36.433Z
<p>Hadoop是一个分布式存储和并行计算综合的系统。分布式存储由HDFS,HBase,Hive等实现,并行计算由MapReduce计算框架实现。</p>
<p>也就是说,Hadoop系统主要分为分布式存储和并行计算部分。集群中一般有一个主控节点,和若干个从节点。分布式存储方面,主控节点维护一个NameNode,存储文件元数据信息,从节点各维护一个DataNode,存储具体的数据。并行计算方面,主控节点维护一个JobTracker守护进程,负责管理和调度任务,从节点各维护一个TaskTracker,负责拉取任务和执行具体的任务。并行计算方面目前广泛采用YARN来管理任务调度,在YARN中,JobTracker的职能由ResourceManager承担,TaskTracker的职能由NodeManager承担。</p>
<p>具体的,YARN中还有ApplicationMaster和Container等概念。</p>
<h3 id="MapReduce"><a href="#MapReduce" class="headerlink" title="MapReduce"></a>MapReduce</h3><p>MapReduce是Hadoop的并行计算部分。MapReduce是一种并行计算的框架和编程模式,提供任务调度,数据和任务互定位,系统优化和容错,提供一套大数据编程接口。</p>
<p>主要参考了Lisp的map/reduce原语设计思想,提供了两个主要的编程接口:Map和Reduce。Map指的是针对不同数据进行同一种操作。Reduce主要是对Map的中间结果进行某种进一步的整理和输出。</p>
<p>MapReduce的数据表示一般为key-value,Map,Reduce函数都是对key-value对来操作。</p>
<p>Map的功能逻辑主要是对输入key-value进行一些处理,产生新的key-value对。<br>每个map对分别对划分的部分数据进行处理,多个map并行处理。<br>Reduce的功能逻辑主要是对map输出的每组(key, values)进行整理和计算,最后产生新的key-value对。</p>
<p>为了解决两个问题,MapReduce框架还提供了另外两个类来增强MapReduce的能力。</p>
<p>问题1:多个重复的key发到网络上进行shuffle,带来许多不必要的传输。<br>解决方案:Combiner会在本地先进行相同key的合并,保证最后不会有重复的key。</p>
<p>问题2:如果不进行恰当的分区,可能会出现同一个key被划分到不同的reduce节点,造成数据的相关性,从而无法产生正确的统计。<br>解决方案:Partitioner会对中间结果key-value进行恰当的分区处理,使得reduce节点分到的数据一定互不相关。</p>
<p>Combiner作用在Map完成计算之后,输出结果之前。<br>Partitioner作用在Map输出结果之后,发送到Reduce节点之前。</p>
<p>一般来说,添加了Combiner和Partitioner的MapReduce模型才是完整的。<br>完整的MapReduce计算框图见《深入理解大数据》P96。</p>
<p>MapReduce实现计算向数据迁移的方式:<br>优先调度那些输入数据在本地节点的Map任务到本地节点。<br>具体的,JobScheduler维护了许多Java Map数据结构,存储任务和任务输入数据所在节点。<br>如果数据A存在于node1,node2,node3上,三个副本,A又是任务T的输入,那么node1,node2,node3这三个节点的可分配任务集中都会包含A。</p>
<p>MapReduce有三种调度方法:</p>
<p>1)FIFO先进先出调度。<br>2)公平调度,公平调度又实现了两级延迟调度,即如果不能调度到本地节点,则进行一段时间的等待,如果还没有则准备调度到rack机器上,如果rack机器也不闲,那么又稍微等一下,如果实在是大家都没空,只有调度到本机架外的任意机器上了。<br>3)计算能力调度,考虑内存等资源能否满足的调度方式。</p>
<p>MapReduce主要组件和编程接口</p>
<p>主要组件有InputFormat,描述了MapReduce作业的输入格式。<br>有三个任务:<br>1)验证输入形式和格式<br>2)将输入数据分割成若干逻辑意义上的InputSplit,每个InputSplit都是一个Mapper的输入<br>3)提供RecordReader,将Mapper的输入(InputSplit)处理转化为若干输入记录(key-value对)</p>
<p>InputFormat确定了,那么InputSplit和RecordReader的类型也都确定了。</p>
<p>事实上作业的Mapper数量是由InputSplit数量决定的。</p>
<p>map函数中有一个context,表示的是环境对象参数,供程序访问Hadoop的环境对象。</p>
<p>Mapper输出前,系统会默认提供一个Combiner对输出结果进行合并,以免造成不必要的网络传输。当然也可以自己定义。但是要注意,Combiner不能改变Mapper输出的键值对的数据类型,否则用于不用Combiner的结果不再一致。有时可以直接拿Reducer当Combiner用。<br>Mapper输出后,系统也会默认提供一个Partitioner对输出结果进行分组,确保同一key的数据分到同一个Reduce节点。<br>实际执行时,Combiner实在Map节点上执行的,而Partitioner和Sort实在Reduce节点上执行的。<br>Sort是一个容易被忽略的过程,在Mapper产生的中间结果发送到Reducer之前,系统会对中间结果(表现为本地磁盘的一个或多个文件)每个文件进行快速排序,然后对这些文件进行归并排序,合并成一个大文件。</p>
<h3 id="HDFS"><a href="#HDFS" class="headerlink" title="HDFS"></a>HDFS</h3><p>HDFS启动,NameNode进入安全模式,检查DataNode上的数据块,如果安全的数据块比例达到要求,则退出安全模式,正式启动了HDFS。</p>
<p>NameNode容错通过SecondaryNameNode的备份来实现。主要通过文件镜像FsImage和编辑日志EditLog来实现检查点Checkpoint机制。<br>fsImage和edits.log会周期性的进行合并,由SecondaryNameNode来完成,为NameNode减轻压力。<br>过程如下:<br><figure class="highlight vbnet"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">secondary namenode请求主Namenode停止使用edits文件,暂时将新的写操作记录到一个新文件中,如edits.<span class="keyword">new</span>。 </span><br><span class="line">secondary namenode节点从主Namenode节点获取fsimage和edits文件(采用HTTP <span class="keyword">GET</span>) </span><br><span class="line">secondary namenode将fsimage文件载入到内存,逐一执行edits文件中的操作,创建新的fsimage文件</span><br><span class="line">secondary namenode将新的fsimage文件发送回主Namenode(使用HTTP POST) </span><br><span class="line">主Namenode节点将从secondary namenode节点接收的fsimage文件替换旧的fsimage文件,用步骤<span class="number">1</span>产生的edits.<span class="keyword">new</span>文件替换旧的edits文件(即改名)。同时更新fstime文件来记录检查点执行的时间</span><br></pre></td></tr></table></figure></p>
<h3 id="FQA"><a href="#FQA" class="headerlink" title="FQA"></a>FQA</h3><ul>
<li><strong>Hadoop的各角色的职能?</strong></li>
</ul>
<p>集群观点上的角色?还是整个Hadoop系统上各个组件观点上的角色?<br>集群观点上,JobTracker, TaskTracker, NameNode, DataNode等的职能如上<a href="#Hadoop">Hadoop</a>节所见。<br>系统观点上,HBase,Hive,YARN,MapReduce,HDFS等各司职要也已简要说明。</p>
<ul>
<li><strong>Hadoop的Checkpoint的作用?</strong></li>
</ul>
<p>保证容错,保证NameNode挂掉后能够从比较近的检查点恢复。<br>具体容错过程见上<a href="#Hadoop">Hadoop</a>节。</p>
<ul>
<li><strong>Hadoop数据块为什么要设置64MB或者更大?</strong></li>
</ul>
<p>减少寻址开销,因为每次寻址都要经过NameNode -> DataNode,然后交换数据。</p>
<ul>
<li><strong>Hadoop的高可靠性如何保证?</strong></li>
</ul>
<p>Hadoop主要包含两个部分,HDFS和MapReduce。<br>HDFS方面,又包含两种节点,NameNode和DataNode。<br>为了保证NameNode的可靠性,HDFS采取了SecondaryNameNode来备份元数据,并周期性的设置<strong>检查点</strong>的方式。<br>为了保证DataNode挂掉,数据访问的可靠性,HDFS采取了<strong>多副本</strong>数据存储,保证单节点挂掉不影响数据的获取。<br>整体方面,采取了<strong>心跳机制</strong>来保证互相通信和失效节点及时检测,并实现了<strong>副本重新创建</strong>机制。<br>文件访问采取<strong>租约</strong>机制。<br>数据一致性采取<strong>校验和</strong>机制。<br>以及采取了<strong>版本回滚</strong>策略。</p>
<p>MapReduce方面,TaskTracker会周期性的向JobTracker发送<strong>心跳</strong>信息,JobTracker能够及时检测TaskTracker的失效。<br>JobTracker的可靠性问题可以依靠<strong>Zookeeper</strong>来优化。<br>采用YARN能够更好地保证MapReduce框架的可靠性。</p>
<ul>
<li><strong>MapReduce中的Text类型和LongWritable类型能不能用String 或者用long类型代替?</strong></li>
</ul>
<p>不能,InputFormat一旦确定,其Mapper的输入格式也就确定了,不能改为其他类型。Text和String虽然很像,但是Text是针对于UTF-8的writable类型,String采用的是Java Char,二者并不一致。</p>
<ul>
<li><strong>MapReduce常用的接口?</strong></li>
</ul>
<p>InputFormat, OutputFormat, RecordReader, RecordWriter, Mapper, Reducer, map, reduce, Combiner, Sorter, Partitioner等。</p>
<ul>
<li><strong>MapReduce的工作流程?</strong></li>
</ul>
<p>InputFormat将输入数据读入,验证输入数据的形式和格式,将数据分割为InputSplit,用RecordReader将InputSplit转化为一条条的记录(Key-value对)。<br>Mapper以各自的InputSplit作为输入,map函数对InputSplit中的记录(key-value)逐个处理,生成新的key-value对,然后由Combiner将这些key-value对进行整理,将相同key的values合并,变成一个key-value,然后输出中间结果到磁盘,中间结果经过Partitioner的分组,被发送到合适的Reduce节点上,传入Reducer之前还会自动将所有key-value按照key值排序,然后结果进入Reducer,reduce函数对(key, values)进行计算以后,经过OutputFormat对象指定输出的格式,将数据写回HDFS中。</p>
<ul>
<li><strong>MapReduce优化方式?</strong></li>
</ul>
<p>对Reduce节点个数进行优化。<br>将负载重的工作尽量使用多个Reduce节点来做,适当分割阶段,但是阶段不要分割太多,否则有多次磁盘开销,权衡下尽量减少不必要的阶段拆分。<br>尽量压缩数据结构,能用IntWritable就不用LongWritable。<br>适当使用变长IntWritable(VIntWritable)和变长LongWritable(VLongWritable),变长xxWritable可以根据实际数值决定存储的位数,在数字分布不均匀的情况下推荐用变长的Writable。</p>
<p>总的来说就是三点:<br>1)Reduce节点个数<br>2)阶段<br>3)数据结构</p>
<ul>
<li><strong>什么样的情况下不能用MapReduce?</strong></li>
</ul>
<p>1)无法表示成key-value的数据,无法将操作分解为map,reduce两个操作的<br>2)不是操作相同,处理数据不同,而是数据间有相关的应用<br>3)多次迭代的任务不适合用MapReduce</p>
<ul>
<li><strong>HDFS的架构?</strong></li>
</ul>
<p>包含一个主控节点NameNode和一组DataNode从节点。<br>NameNode管理整个文件系统的元数据和命名空间,并负责相应外部访问请求。<br>DataNode负责具体数据块的存储,负责具体的数据读写请求,创建删除数据块等。</p>
<ul>
<li><strong>HDFS优化?</strong></li>
</ul>
<p>1)尽可能使用二进制的Writable类型,减小存储空间<br>2)副本数<br>3)块大小<br>等等【可补充】</p>
<p>Hadoop是一个分布式存储和并行计算综合的系统。分布式存储由HDFS,HBase,Hive等实现,并行计算由MapReduce计算框架实现。</p>
<p>也就是说,Hadoop系统主要分为分布式存储和并行计算部分。集群中一般有一个主控节点,和若干个从节点。分布式存储方面
Ray源码解析之调度部分
http://whatbeg.com/2019/04/16/raysource-schedule.html
2019-04-16T13:00:07.000Z
2019-04-16T13:12:35.333Z
<p>Ray Version 0.4.0</p>
<p>调度部分包括六个文件:</p>
<ul>
<li>调度策略:头文件<code>scheduling_policy.h</code>(定义SchedulingPolicy)和cpp文件<code>scheduling_policy.cc</code>(为SchedulingPolicy实现调度策略)。</li>
<li>调度队列:头文件<code>scheduling_queue.h</code>()和cpp文件<code>scheduling_queue.cc</code>()。</li>
<li>调度资源:头文件<code>scheduling_resources.h</code>()和cpp文件<code>scheduling_resources.cc</code>()。</li>
</ul>
<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line">src/ray/raylet/scheduling_policy.h</span><br><span class="line">---------------------------------------------</span><br><span class="line"></span><br><span class="line"><span class="keyword">namespace</span> ray {</span><br><span class="line"></span><br><span class="line"> <span class="comment">/// \brief Implements a scheduling policy for the node manager.</span></span><br><span class="line"> <span class="keyword">class</span> SchedulingPolicy {</span><br><span class="line"> <span class="keyword">public</span>:</span><br><span class="line"> <span class="comment">/// \param scheduling_queue: reference to a scheduler queues object for access to</span></span><br><span class="line"> <span class="comment">/// tasks.</span></span><br><span class="line"> SchedulingPolicy(<span class="keyword">const</span> SchedulingQueue &scheduling_queue);</span><br><span class="line"> </span><br><span class="line"> <span class="comment">/// Perform a scheduling operation, given a set of cluster resources and</span></span><br><span class="line"> <span class="comment">/// producing a mapping of tasks to node managers.</span></span><br><span class="line"> <span class="comment">///</span></span><br><span class="line"> <span class="comment">/// \param cluster_resources: a set of cluster resources representing</span></span><br><span class="line"> <span class="comment">/// configured and current resource capacity on each node.</span></span><br><span class="line"> <span class="comment">/// \return Scheduling decision, mapping tasks to node managers for placement.</span></span><br><span class="line"> <span class="built_in">std</span>::<span class="built_in">unordered_map</span><TaskID, ClientID, UniqueIDHasher> Schedule(</span><br><span class="line"> <span class="keyword">const</span> <span class="built_in">std</span>::<span class="built_in">unordered_map</span><ClientID, SchedulingResources, UniqueIDHasher></span><br><span class="line"> &cluster_resources);</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">virtual</span> ~SchedulingPolicy();</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">private</span>:</span><br><span class="line"> <span class="comment">/// An immutable reference to the scheduling task queues.</span></span><br><span class="line"> <span class="keyword">const</span> SchedulingQueue &scheduling_queue_;</span><br><span class="line"> };</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>调度策略的头文件,描述了SchedulingPolicy类的结构,包括构造函数,析构函数,Schedule函数(关键调度决策)。<br>构造函数传入需要调度的任务队列,Schedule函数接受集群资源的一个<code>unordered_map</code>作为输入,输出为调度决策,也是一个<code>unordered_map</code>。<code>unordered_map</code>是C++ 11的新特性,其内部元素是无序的。<br>Schedule接受的输入map是以ClientID即本节点id为key,SchedulingResources调度资源为value,即一个<code>节点->资源</code>的映射。<br>Schedule输出map则是以TaskID为键,ClientID为值,即表示TaskID表示的Task调度到ClientID代表的节点上。</p>
<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line">scheduling_policy.cc</span><br><span class="line">----------------------------------</span><br><span class="line"></span><br><span class="line"><span class="keyword">namespace</span> ray {</span><br><span class="line">....</span><br><span class="line"> <span class="built_in">std</span>::<span class="built_in">unordered_map</span><TaskID, ClientID, UniqueIDHasher> SchedulingPolicy::Schedule(</span><br><span class="line"> <span class="keyword">const</span> <span class="built_in">std</span>::<span class="built_in">unordered_map</span><ClientID, SchedulingResources, UniqueIDHasher></span><br><span class="line"> &cluster_resources) {</span><br><span class="line"> <span class="keyword">static</span> ClientID local_node_id = ClientID::nil();</span><br><span class="line"> <span class="built_in">std</span>::<span class="built_in">unordered_map</span><TaskID, ClientID, UniqueIDHasher> decision;</span><br><span class="line"> <span class="comment">// TODO(atumanov): consider all cluster resources.</span></span><br><span class="line"> SchedulingResources resource_supply = cluster_resources.at(local_node_id);</span><br><span class="line"> <span class="keyword">const</span> <span class="keyword">auto</span> &resource_supply_set = resource_supply.GetAvailableResources();</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// Iterate over running tasks, get their resource demand and try to schedule.</span></span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">const</span> <span class="keyword">auto</span> &t : scheduling_queue_.GetReadyTasks()) {</span><br><span class="line"> <span class="comment">// Get task's resource demand</span></span><br><span class="line"> <span class="keyword">const</span> <span class="keyword">auto</span> &resource_demand = t.GetTaskSpecification().GetRequiredResources();</span><br><span class="line"> <span class="keyword">bool</span> task_feasible = resource_demand.IsSubset(resource_supply_set);</span><br><span class="line"> <span class="keyword">if</span> (task_feasible) {</span><br><span class="line"> <span class="keyword">const</span> TaskID &task_id = t.GetTaskSpecification().TaskId();</span><br><span class="line"> decision[task_id] = local_node_id;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> decision;</span><br><span class="line"> }</span><br><span class="line">....</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>为简洁起见,这里之贴出了Schedule函数的实现。<br>从上述代码可以看到,ray的调度包括这么几个过程:<br>1)得到本地节点id<br>2)得到本地节点可提供的资源<br>3)对于每个准备好的任务,判断本地资源是否能满足该任务(task_feasible),能满足则调度到本地节点。</p>
<p>【问】那么不能满足的任务呢?这些任务没有对应的<code>local_node_id</code>,在decision中也就没有key,这部分任务该怎么办,有待后面分析。</p>
<p>【问】还有一个问题就是,程序中本地节点能满足资源要求就调度到本地节点,调度后并不会减少<code>resource_supply</code>即资源的供给,那么如果本地节点能满足所有任务的要求,岂不是所有任务都调度到此节点?</p>
<p>这里<code>resource_demand.IsSubset(resource_supply_set)</code>中的<code>IsSubset</code>表示资源需求是否是资源供给集的子集,如果是,表示满足条件。</p>
<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line">scheduling_queue.h</span><br><span class="line">--------------------------------</span><br><span class="line"></span><br><span class="line"><span class="keyword">namespace</span> ray {</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> SchedulingQueue {</span><br><span class="line"> <span class="keyword">public</span>:</span><br><span class="line"> SchedulingQueue() {}</span><br><span class="line"></span><br><span class="line"> <span class="keyword">virtual</span> ~SchedulingQueue() {}</span><br><span class="line"></span><br><span class="line"> <span class="keyword">const</span> <span class="built_in">std</span>::<span class="built_in">list</span><Task> &GetWaitingTasks() <span class="keyword">const</span>;</span><br><span class="line"> <span class="keyword">const</span> <span class="built_in">std</span>::<span class="built_in">list</span><Task> &GetReadyTasks() <span class="keyword">const</span>;</span><br><span class="line"> <span class="keyword">const</span> <span class="built_in">std</span>::<span class="built_in">list</span><Task> &GetReadyMethods() <span class="keyword">const</span>;</span><br><span class="line"> <span class="keyword">const</span> <span class="built_in">std</span>::<span class="built_in">list</span><Task> &GetScheduledTasks() <span class="keyword">const</span>;</span><br><span class="line"> <span class="keyword">const</span> <span class="built_in">std</span>::<span class="built_in">list</span><Task> &GetRunningTasks() <span class="keyword">const</span>;</span><br><span class="line"></span><br><span class="line"> <span class="built_in">std</span>::<span class="built_in">vector</span><Task> RemoveTasks(<span class="built_in">std</span>::<span class="built_in">unordered_set</span><TaskID, UniqueIDHasher> tasks);</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">void</span> <span class="title">QueueWaitingTasks</span><span class="params">(<span class="keyword">const</span> <span class="built_in">std</span>::<span class="built_in">vector</span><Task> &tasks)</span></span>;</span><br><span class="line"> <span class="function"><span class="keyword">void</span> <span class="title">QueueReadyTasks</span><span class="params">(<span class="keyword">const</span> <span class="built_in">std</span>::<span class="built_in">vector</span><Task> &tasks)</span></span>;</span><br><span class="line"> <span class="function"><span class="keyword">void</span> <span class="title">QueueScheduledTasks</span><span class="params">(<span class="keyword">const</span> <span class="built_in">std</span>::<span class="built_in">vector</span><Task> &tasks)</span></span>;</span><br><span class="line"> <span class="function"><span class="keyword">void</span> <span class="title">QueueRunningTasks</span><span class="params">(<span class="keyword">const</span> <span class="built_in">std</span>::<span class="built_in">vector</span><Task> &tasks)</span></span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">/// Register an actor.</span></span><br><span class="line"> <span class="comment">///</span></span><br><span class="line"> <span class="comment">/// \param actor_id The ID of the actor to register.</span></span><br><span class="line"> <span class="comment">/// \param actor_information Information about the actor.</span></span><br><span class="line"> <span class="function"><span class="keyword">bool</span> <span class="title">RegisterActor</span><span class="params">(ActorID actor_id, <span class="keyword">const</span> ActorInformation &actor_information)</span></span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span>:</span><br><span class="line"> <span class="built_in">std</span>::<span class="built_in">list</span><Task> waiting_tasks_;</span><br><span class="line"> <span class="built_in">std</span>::<span class="built_in">list</span><Task> ready_tasks_;</span><br><span class="line"> <span class="built_in">std</span>::<span class="built_in">list</span><Task> scheduled_tasks_;</span><br><span class="line"> <span class="built_in">std</span>::<span class="built_in">list</span><Task> running_tasks_;</span><br><span class="line"> <span class="comment">/// The registry of known actors.</span></span><br><span class="line"> <span class="built_in">std</span>::<span class="built_in">unordered_map</span><ActorID, ActorInformation, UniqueIDHasher> actor_registry_;</span><br><span class="line">};</span><br><span class="line">} <span class="comment">// namespace ray</span></span><br></pre></td></tr></table></figure>
<p>调度队列定义头文件,封装了调度队列,每个队列包含着各自类型的任务。</p>
<p>(1) waiting: for object dependencies to become available,<br>(2) ready: object dependencies are available and the task is ready to be scheduled<br>(3) scheduled: the task has been scheduled but is waiting for a worker<br>(4) running: the task has been scheduled and is running on a worker.</p>
<p><code>scheduling_queue.cc</code>文件中则实现了这些队列的getter方法,以及进队列方法,移除方法。<br>比如说移除方法:<br><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Helper function to remove tasks in the given set of task_ids from a</span></span><br><span class="line"><span class="comment">// queue, and append them to the given vector removed_tasks.</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">removeTasksFromQueue</span><span class="params">(<span class="built_in">std</span>::<span class="built_in">list</span><Task> &<span class="built_in">queue</span>,</span><br><span class="line"> <span class="built_in">std</span>::<span class="built_in">unordered_set</span><TaskID, UniqueIDHasher> &task_ids,</span><br><span class="line"> <span class="built_in">std</span>::<span class="built_in">vector</span><Task> &removed_tasks)</span> </span>{</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">auto</span> it = <span class="built_in">queue</span>.begin(); it != <span class="built_in">queue</span>.end();) {</span><br><span class="line"> <span class="keyword">auto</span> task_id = task_ids.find(it->GetTaskSpecification().TaskId());</span><br><span class="line"> <span class="keyword">if</span> (task_id != task_ids.end()) {</span><br><span class="line"> task_ids.erase(task_id);</span><br><span class="line"> removed_tasks.push_back(<span class="built_in">std</span>::move(*it));</span><br><span class="line"> it = <span class="built_in">queue</span>.erase(it);</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> it++;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p>
<p>从queue中移除task_ids中包含的所有taskID代表的task。</p>
<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br></pre></td><td class="code"><pre><span class="line">scheduling_resources.h</span><br><span class="line">------------------------------------</span><br><span class="line"></span><br><span class="line"><span class="keyword">namespace</span> ray {</span><br><span class="line"></span><br><span class="line"><span class="comment">/// Resource availability status reports whether the resource requirement is</span></span><br><span class="line"><span class="comment">/// (1) infeasible, (2) feasible but currently unavailable, or (3) available.</span></span><br><span class="line"><span class="keyword">typedef</span> <span class="keyword">enum</span> {</span><br><span class="line"> kInfeasible, <span class="comment">///< Cannot ever satisfy resource requirements.</span></span><br><span class="line"> kResourcesUnavailable, <span class="comment">///< Feasible, but not currently available.</span></span><br><span class="line"> kFeasible <span class="comment">///< Feasible and currently available.</span></span><br><span class="line">} ResourceAvailabilityStatus;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> ResourceSet {</span><br><span class="line"> <span class="keyword">public</span>:</span><br><span class="line"> ResourceSet();</span><br><span class="line"> ResourceSet(<span class="keyword">const</span> <span class="built_in">std</span>::<span class="built_in">unordered_map</span><<span class="built_in">std</span>::<span class="built_in">string</span>, <span class="keyword">double</span>> &resource_map);</span><br><span class="line"> ~ResourceSet();</span><br><span class="line"></span><br><span class="line"> <span class="keyword">bool</span> <span class="keyword">operator</span>==(<span class="keyword">const</span> ResourceSet &rhs) <span class="keyword">const</span>;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">bool</span> <span class="title">IsEqual</span><span class="params">(<span class="keyword">const</span> ResourceSet &other)</span> <span class="keyword">const</span></span>;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">bool</span> <span class="title">IsSubset</span><span class="params">(<span class="keyword">const</span> ResourceSet &other)</span> <span class="keyword">const</span></span>;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">bool</span> <span class="title">IsSuperset</span><span class="params">(<span class="keyword">const</span> ResourceSet &other)</span> <span class="keyword">const</span></span>;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">bool</span> <span class="title">AddResource</span><span class="params">(<span class="keyword">const</span> <span class="built_in">std</span>::<span class="built_in">string</span> &resource_name, <span class="keyword">double</span> capacity)</span></span>;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">bool</span> <span class="title">RemoveResource</span><span class="params">(<span class="keyword">const</span> <span class="built_in">std</span>::<span class="built_in">string</span> &resource_name)</span></span>;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">bool</span> <span class="title">AddResources</span><span class="params">(<span class="keyword">const</span> ResourceSet &other)</span></span>;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">bool</span> <span class="title">SubtractResources</span><span class="params">(<span class="keyword">const</span> ResourceSet &other)</span></span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 返回指定资源的容量值(赋给value指向的值),如果资源有大于0的数量,且value不为空指针,则返回true</span></span><br><span class="line"> <span class="function"><span class="keyword">bool</span> <span class="title">GetResource</span><span class="params">(<span class="keyword">const</span> <span class="built_in">std</span>::<span class="built_in">string</span> &resource_name, <span class="keyword">double</span> *value)</span> <span class="keyword">const</span></span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span>:</span><br><span class="line"> <span class="comment">// 资源容量map</span></span><br><span class="line"> <span class="built_in">std</span>::<span class="built_in">unordered_map</span><<span class="built_in">std</span>::<span class="built_in">string</span>, <span class="keyword">double</span>> resource_capacity_;</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="comment">/// \class SchedulingResources</span></span><br><span class="line"><span class="comment">/// SchedulingResources 封装资源的状态和资源的计数。资源包括配置资源束容量和GPU分配图。</span></span><br><span class="line"><span class="keyword">class</span> SchedulingResources {</span><br><span class="line"> <span class="keyword">public</span>:</span><br><span class="line"> SchedulingResources();</span><br><span class="line"> SchedulingResources(<span class="keyword">const</span> ResourceSet &total);</span><br><span class="line"> ~SchedulingResources();</span><br><span class="line"> <span class="comment">// 检查一个资源集是否能被满足,有几种状态,(1) infeasible, (2) feasible but currently unavailable, or (3) available.</span></span><br><span class="line"> <span class="function">ResourceAvailabilityStatus <span class="title">CheckResourcesSatisfied</span><span class="params">(ResourceSet &<span class="built_in">set</span>)</span> <span class="keyword">const</span></span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 请求现在可用的资源集,返回一个不可变的资源集合</span></span><br><span class="line"> <span class="function"><span class="keyword">const</span> ResourceSet &<span class="title">GetAvailableResources</span><span class="params">()</span> <span class="keyword">const</span></span>;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">bool</span> <span class="title">Release</span><span class="params">(<span class="keyword">const</span> ResourceSet &resources)</span></span>;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">bool</span> <span class="title">Acquire</span><span class="params">(<span class="keyword">const</span> ResourceSet &resources)</span></span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span>:</span><br><span class="line"> <span class="comment">/// 静态资源配置</span></span><br><span class="line"> ResourceSet resources_total_;</span><br><span class="line"> <span class="comment">/// 动态资源容量</span></span><br><span class="line"> ResourceSet resources_available_;</span><br><span class="line"> <span class="comment">/// gpu_map - replace with ResourceMap (for generality).</span></span><br><span class="line">};</span><br><span class="line"></span><br><span class="line">} <span class="comment">// namespace ray</span></span><br></pre></td></tr></table></figure>
<p>调度资源定义中包含两个类,一个是资源集(ResourceSet),资源集维护了一个<code>资源名->容量</code>的map映射<code>resource_capacity_</code>,并实现了相等,资源子集和超集,添加和删除资源等接口。<br>另一个是调度资源类(SchedulingResources),维护了两个资源集,一个是<code>resources_total_</code>总的静态资源配置,描述集群总共有哪些资源,以及一个<code>resources_available_</code>可用资源集,每个时刻的可用资源量是动态变化的,所以也叫动态资源集。实现了检查资源能够满足,请求现在可用的资源,申请(Acquire)和释放(Release)等接口。<br>申请(Acquire)和释放(Release)接口会分别调用资源集的AddResources,RemoveResources接口。</p>
<p>在<code>scheduling_resources.cc</code>中,资源集的相等是通过判断是否互为子集来实现的:<br><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">bool</span> ResourceSet::<span class="keyword">operator</span>==(<span class="keyword">const</span> ResourceSet &rhs) <span class="keyword">const</span> {</span><br><span class="line"> <span class="keyword">return</span> (<span class="keyword">this</span>->IsSubset(rhs) && rhs.IsSubset(*<span class="keyword">this</span>));</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p>
<p>资源集是否为另一资源集的子集的判断函数如下实现:<br><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">bool</span> ResourceSet::IsSubset(<span class="keyword">const</span> ResourceSet &other) <span class="keyword">const</span> {</span><br><span class="line"> <span class="comment">// Check to make sure all keys of this are in other.</span></span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">const</span> <span class="keyword">auto</span> &resource_pair : resource_capacity_) {</span><br><span class="line"> <span class="keyword">const</span> <span class="keyword">auto</span> &resource_name = resource_pair.first;</span><br><span class="line"> <span class="keyword">const</span> <span class="keyword">double</span> lhs_quantity = resource_pair.second;</span><br><span class="line"> <span class="keyword">double</span> rhs_quantity = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">if</span> (!other.GetResource(resource_name, &rhs_quantity)) {</span><br><span class="line"> <span class="comment">// Resource not found in rhs, therefore lhs is not a subset of rhs.</span></span><br><span class="line"> <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (lhs_quantity > rhs_quantity) {</span><br><span class="line"> <span class="comment">// Resource found in rhs, but lhs capacity exceeds rhs capacity.</span></span><br><span class="line"> <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p>
<p>看完后面4个文件再回过头去看调度策略的实现就明了多了。</p>
<p>Ray Version 0.4.0</p>
<p>调度部分包括六个文件:</p>
<ul>
<li>调度策略:头文件<code>scheduling_policy.h</code>(定义SchedulingPolicy)和cpp文件<code>scheduling_poli
Ray源码解析之Task部分
http://whatbeg.com/2019/04/16/raysource-task.html
2019-04-16T12:58:11.000Z
2019-04-16T13:26:45.173Z
<p>Ray Version 0.4.0</p>
<p>task头文件描述了Task的数据结构。<br>Task代表了一个Ray的任务及其规格,包括执行时获得的可变的规格和提交时就确定的不可变的规格。分别由<code>TaskExecutionSpecification</code>和<code>TaskSpecification</code>表示。初始化Task需提供这两种规格。Task中有获得这两种规格的函数。<br>Task同时维护了任务的对象依赖,包括不可变的任务参数以及可变的执行时依赖。也实现了getter。Task还包含一个判断task是否依赖于某对象的函数。</p>
<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br></pre></td><td class="code"><pre><span class="line">src/ray/raylet/task.h</span><br><span class="line">----------------------------------</span><br><span class="line"></span><br><span class="line"><span class="keyword">namespace</span> ray {</span><br><span class="line"></span><br><span class="line"><span class="comment">/// A Task represents a Ray task and a specification of its execution (e.g.,</span></span><br><span class="line"><span class="comment">/// resource demands). The task's specification contains both immutable fields,</span></span><br><span class="line"><span class="comment">/// determined at submission time, and mutable fields, determined at execution</span></span><br><span class="line"><span class="comment">/// time.</span></span><br><span class="line"><span class="keyword">class</span> Task {</span><br><span class="line"> <span class="keyword">public</span>:</span><br><span class="line"> <span class="comment">/// Create a task.</span></span><br><span class="line"> <span class="comment">///</span></span><br><span class="line"> <span class="comment">/// \param execution_spec The execution specification for the task. These are</span></span><br><span class="line"> <span class="comment">/// the mutable fields in the task specification that may change at task</span></span><br><span class="line"> <span class="comment">/// execution time.</span></span><br><span class="line"> <span class="comment">/// \param task_spec The immutable specification for the task. These fields</span></span><br><span class="line"> <span class="comment">/// are determined at task submission time.</span></span><br><span class="line"> Task(<span class="keyword">const</span> TaskExecutionSpecification &execution_spec,</span><br><span class="line"> <span class="keyword">const</span> TaskSpecification &task_spec)</span><br><span class="line"> : task_execution_spec_(execution_spec), task_spec_(task_spec) {}</span><br><span class="line"></span><br><span class="line"> <span class="comment">/// Destroy the task.</span></span><br><span class="line"> <span class="keyword">virtual</span> ~Task() {}</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 获取任务执行规格</span></span><br><span class="line"> <span class="function"><span class="keyword">const</span> TaskExecutionSpecification &<span class="title">GetTaskExecutionSpec</span><span class="params">()</span> <span class="keyword">const</span></span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 获取任务不可变的规格</span></span><br><span class="line"> <span class="function"><span class="keyword">const</span> TaskSpecification &<span class="title">GetTaskSpecification</span><span class="params">()</span> <span class="keyword">const</span></span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">/// Get the task's object dependencies. This comprises the immutable task</span></span><br><span class="line"> <span class="comment">/// arguments and the mutable execution dependencies.</span></span><br><span class="line"> <span class="comment">///</span></span><br><span class="line"> <span class="comment">/// \return The object dependencies.</span></span><br><span class="line"> <span class="comment">/// TODO(atumanov): consider returning a constant reference.</span></span><br><span class="line"> <span class="keyword">const</span> <span class="built_in">std</span>::<span class="built_in">vector</span><ObjectID> GetDependencies() <span class="keyword">const</span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 判断task是否依赖于某一个object</span></span><br><span class="line"> <span class="function"><span class="keyword">bool</span> <span class="title">DependsOn</span><span class="params">(<span class="keyword">const</span> ObjectID &object_id)</span> <span class="keyword">const</span></span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span>:</span><br><span class="line"> <span class="comment">/// Task execution specification, consisting of all dynamic/mutable</span></span><br><span class="line"> <span class="comment">/// information about this task determined at execution time..</span></span><br><span class="line"> TaskExecutionSpecification task_execution_spec_;</span><br><span class="line"> <span class="comment">/// Task specification object, consisting of immutable information about this</span></span><br><span class="line"> <span class="comment">/// task determined at submission time. Includes resource demand, object</span></span><br><span class="line"> <span class="comment">/// dependencies, etc.</span></span><br><span class="line"> TaskSpecification task_spec_;</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line">} <span class="comment">// namespace ray</span></span><br></pre></td></tr></table></figure>
<p>分析<code>task.cc</code>之前先来看看两种规格的定义:</p>
<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><span class="line">src/ray/raylet/task_execution_spec.h <span class="meta"># 执行时任务规格头文件 </span></span><br><span class="line">----------------------</span><br><span class="line"><span class="keyword">namespace</span> ray {</span><br><span class="line"></span><br><span class="line"><span class="comment">/// The task execution specification encapsulates all mutable information about</span></span><br><span class="line"><span class="comment">/// the task. These fields may change at execution time, converse to the</span></span><br><span class="line"><span class="comment">/// TaskSpecification that is determined at submission time.</span></span><br><span class="line"><span class="keyword">class</span> TaskExecutionSpecification {</span><br><span class="line"> <span class="keyword">public</span>:</span><br><span class="line"> TaskExecutionSpecification(<span class="keyword">const</span> <span class="built_in">std</span>::<span class="built_in">vector</span><ObjectID> &&execution_dependencies);</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// execution_dependencies: 任务依赖</span></span><br><span class="line"> <span class="comment">// spillback_count: 回溢次数,即由local scheduler像global scheduler推此任务的次数</span></span><br><span class="line"> TaskExecutionSpecification(<span class="keyword">const</span> <span class="built_in">std</span>::<span class="built_in">vector</span><ObjectID> &&execution_dependencies,</span><br><span class="line"> <span class="keyword">int</span> spillback_count);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">const</span> <span class="built_in">std</span>::<span class="built_in">vector</span><ObjectID> &ExecutionDependencies() <span class="keyword">const</span>;</span><br><span class="line"> </span><br><span class="line"> <span class="function"><span class="keyword">void</span> <span class="title">SetExecutionDependencies</span><span class="params">(<span class="keyword">const</span> <span class="built_in">std</span>::<span class="built_in">vector</span><ObjectID> &dependencies)</span></span>;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">int</span> <span class="title">SpillbackCount</span><span class="params">()</span> <span class="keyword">const</span></span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">/// Increment the spillback count for this task.</span></span><br><span class="line"> <span class="function"><span class="keyword">void</span> <span class="title">IncrementSpillbackCount</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">/// Get the task's last timestamp (ms).</span></span><br><span class="line"> <span class="comment">/// \return The timestamp when this task was last received for scheduling.</span></span><br><span class="line"> <span class="keyword">int64_t</span> LastTimeStamp() <span class="keyword">const</span>;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">void</span> <span class="title">SetLastTimeStamp</span><span class="params">(int64_t new_timestamp)</span></span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span>:</span><br><span class="line"> <span class="comment">/// 任务所依赖的object id的列表</span></span><br><span class="line"> <span class="built_in">std</span>::<span class="built_in">vector</span><ObjectID> execution_dependencies_;</span><br><span class="line"> <span class="comment">/// The last time this task was received for scheduling.</span></span><br><span class="line"> <span class="keyword">int64_t</span> last_timestamp_;</span><br><span class="line"> <span class="comment">/// The number of times this task was spilled back by local schedulers.</span></span><br><span class="line"> <span class="keyword">int</span> spillback_count_;</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line">} <span class="comment">// namespace ray</span></span><br></pre></td></tr></table></figure>
<p>执行时任务规格包含的信息要比提交时任务规格(<code>task_spec.h</code>中定义)少得多,主要包含任务执行时依赖,回溢次数和最后时间戳三个变量。</p>
<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line">src/ray/raylet/task_spec.h</span><br><span class="line">-----------------------------------</span><br><span class="line"> <span class="function">TaskID <span class="title">TaskId</span><span class="params">()</span> <span class="keyword">const</span></span>; <span class="comment">// 本Task的id</span></span><br><span class="line"> <span class="function">UniqueID <span class="title">DriverId</span><span class="params">()</span> <span class="keyword">const</span></span>; <span class="comment">// 表示task所属job</span></span><br><span class="line"> <span class="function">TaskID <span class="title">ParentTaskId</span><span class="params">()</span> <span class="keyword">const</span></span>; <span class="comment">// 启动这个task的task的id</span></span><br><span class="line"> <span class="keyword">int64_t</span> ParentCounter() <span class="keyword">const</span>; <span class="comment">// task的父task在这之前启动的task数量</span></span><br><span class="line"> <span class="function">FunctionID <span class="title">FunctionId</span><span class="params">()</span> <span class="keyword">const</span></span>; <span class="comment">// task所执行的函数id</span></span><br><span class="line"> <span class="keyword">int64_t</span> NumArgs() <span class="keyword">const</span>; <span class="comment">// 参数数量</span></span><br><span class="line"> <span class="keyword">int64_t</span> NumReturns() <span class="keyword">const</span>; <span class="comment">// 返回值数量</span></span><br><span class="line"> <span class="function"><span class="keyword">bool</span> <span class="title">ArgByRef</span><span class="params">(int64_t arg_index)</span> <span class="keyword">const</span></span>; <span class="comment">// </span></span><br><span class="line"> <span class="function"><span class="keyword">int</span> <span class="title">ArgIdCount</span><span class="params">(int64_t arg_index)</span> <span class="keyword">const</span></span>; <span class="comment">// 第arg_index个参数有多少个object id</span></span><br><span class="line"> <span class="function">ObjectID <span class="title">ArgId</span><span class="params">(int64_t arg_index, int64_t id_index)</span> <span class="keyword">const</span></span>; <span class="comment">// 第arg_index个参数的第id_index个object id是多少</span></span><br><span class="line"> <span class="function"><span class="keyword">const</span> uint8_t *<span class="title">ArgVal</span><span class="params">(int64_t arg_index)</span> <span class="keyword">const</span></span>;</span><br><span class="line"> <span class="keyword">size_t</span> ArgValLength(<span class="keyword">int64_t</span> arg_index) <span class="keyword">const</span>;</span><br><span class="line"> <span class="function"><span class="keyword">double</span> <span class="title">GetRequiredResource</span><span class="params">(<span class="keyword">const</span> <span class="built_in">std</span>::<span class="built_in">string</span> &resource_name)</span> <span class="keyword">const</span></span>; <span class="comment">// 获取特定资源的需求</span></span><br><span class="line"> <span class="function"><span class="keyword">const</span> ResourceSet <span class="title">GetRequiredResources</span><span class="params">()</span> <span class="keyword">const</span></span>; <span class="comment">// 获取资源需求集</span></span><br><span class="line"> </span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line"> <span class="comment">/// Task specification constructor from a pointer.</span></span><br><span class="line"> TaskSpecification(<span class="keyword">const</span> <span class="keyword">uint8_t</span> *spec, <span class="keyword">size_t</span> spec_size);</span><br><span class="line"> <span class="comment">/// Get a pointer to the byte data.</span></span><br><span class="line"> <span class="function"><span class="keyword">const</span> uint8_t *<span class="title">data</span><span class="params">()</span> <span class="keyword">const</span></span>;</span><br><span class="line"> <span class="comment">/// Get the size in bytes of the task specification.</span></span><br><span class="line"> <span class="keyword">size_t</span> size() <span class="keyword">const</span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">/// The task specification data.</span></span><br><span class="line"> <span class="built_in">std</span>::<span class="built_in">vector</span><<span class="keyword">uint8_t</span>> spec_;</span><br></pre></td></tr></table></figure>
<p>Ray Version 0.4.0</p>
<p>task头文件描述了Task的数据结构。<br>Task代表了一个Ray的任务及其规格,包括执行时获得的可变的规格和提交时就确定的不可变的规格。分别由<code>TaskExecutionSpecification</cod
Ray源码解析之整体逻辑结构
http://whatbeg.com/2019/04/16/raysource-overall.html
2019-04-16T12:56:27.000Z
2019-04-16T12:59:37.689Z
<p>Ray Version 0.4.0</p>
<p>以一个Ray示例程序来说明,Ray执行多进程/分布式程序的过程。<br><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> ray</span><br><span class="line"><span class="keyword">import</span> time</span><br><span class="line"></span><br><span class="line"><span class="meta">@ray.remote</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">f</span><span class="params">()</span>:</span></span><br><span class="line"> time.sleep(<span class="number">1</span>)</span><br><span class="line"> <span class="keyword">return</span> <span class="number">1</span></span><br><span class="line"></span><br><span class="line">ray.init()</span><br><span class="line">results = ray.get([f.remote() <span class="keyword">for</span> i <span class="keyword">in</span> range(<span class="number">4</span>)])</span><br><span class="line">print(results)</span><br></pre></td></tr></table></figure></p>
<p>结果输出如下:<br><figure class="highlight autoit"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">Process STDOUT <span class="literal">and</span> STDERR is being redirected <span class="keyword">to</span> /tmp/raylogs/.</span><br><span class="line">Waiting <span class="keyword">for</span> redis server at <span class="number">127.0</span><span class="number">.0</span><span class="number">.1</span>:<span class="number">35084</span> <span class="keyword">to</span> respond...</span><br><span class="line">Waiting <span class="keyword">for</span> redis server at <span class="number">127.0</span><span class="number">.0</span><span class="number">.1</span>:<span class="number">59058</span> <span class="keyword">to</span> respond...</span><br><span class="line">Starting <span class="keyword">local</span> scheduler <span class="keyword">with</span> the following resources: {<span class="string">'CPU'</span>: <span class="number">8</span>, <span class="string">'GPU'</span>: <span class="number">0</span>}.</span><br><span class="line"></span><br><span class="line">======================================================================</span><br><span class="line">View the web UI at http://localhost:<span class="number">8888</span>/notebooks/ray_ui26399.ipynb?token=<span class="number">7e19</span>e5b2051ef36474500c26427d304da95835e4f0933992</span><br><span class="line">======================================================================</span><br><span class="line"></span><br><span class="line">[<span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>]</span><br><span class="line"></span><br><span class="line">Process finished <span class="keyword">with</span> <span class="keyword">exit</span> code <span class="number">0</span></span><br></pre></td></tr></table></figure></p>
<p>首先我们定义<code>remote</code>函数<code>f</code>,将一个普通的Python函数变为<code>remote</code>函数只需在其上加上<code>@ray.remote</code>装饰器。</p>
<p><code>remote</code>是一个装饰工厂函数,返回修饰函数的装饰器,主要定义代码如下:<br><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># ray/python/ray/worker.py</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">remote</span><span class="params">(*args, **kwargs)</span>:</span></span><br><span class="line"></span><br><span class="line"> worker = global_worker</span><br><span class="line"> </span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">make_remote_decorator</span><span class="params">(num_return_vals, num_cpus, num_gpus, resources,</span><br><span class="line"> max_calls, checkpoint_interval, func_id=None)</span>:</span> <span class="comment"># 装饰器工厂函数</span></span><br><span class="line"> <span class="comment"># 装饰器,装饰函数,做Actor和function的区分</span></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">remote_decorator</span><span class="params">(func_or_class)</span>:</span> </span><br><span class="line"> <span class="keyword">if</span> inspect.isfunction(func_or_class) <span class="keyword">or</span> is_cython(func_or_class):</span><br><span class="line"> ...</span><br><span class="line"> <span class="keyword">return</span> remote_function_decorator(..) <span class="comment"># 是函数,调用远程函数装饰器</span></span><br><span class="line"> <span class="keyword">if</span> inspect.isclass(func_or_class):</span><br><span class="line"> ...</span><br><span class="line"> <span class="keyword">return</span> worker.make_actor(..) <span class="comment"># 是actor,由全局worker创建一个actor</span></span><br><span class="line"> <span class="keyword">raise</span> Exception</span><br><span class="line"> <span class="comment"># 装饰器,参数是函数,起装饰函数的作用</span></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">remote_function_decorator</span><span class="params">(func, function_properties)</span>:</span></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">func_call</span><span class="params">(*args, **kwargs)</span>:</span></span><br><span class="line"> <span class="keyword">return</span> _submit(args=args, kwargs=kwargs)</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">_submit</span><span class="params">(...)</span>:</span></span><br><span class="line"> ...</span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">func_executor</span><span class="params">(arguments)</span>:</span></span><br><span class="line"> <span class="string">"""This gets run when the remote function is executed."""</span></span><br><span class="line"> result = func(*arguments)</span><br><span class="line"> <span class="keyword">return</span> result</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">func_invoker</span><span class="params">(*args, **kwargs)</span>:</span></span><br><span class="line"> <span class="keyword">raise</span> Exception</span><br><span class="line"> func_invoker.remote = func_call <span class="comment"># func.remote() 直接调用func_call</span></span><br><span class="line"> func_invoker._submit = _submit</span><br><span class="line"> func_invoker.executor = func_executor</span><br><span class="line"> func_invoker.is_remote = <span class="keyword">True</span></span><br><span class="line"> func_name = <span class="string">"{}.{}"</span>.format(func.__module__, func.__name__)</span><br><span class="line"> func_invoker.func_name = func_name</span><br><span class="line"> ...</span><br><span class="line"> <span class="keyword">return</span> func_invoker</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> remote_decorator</span><br><span class="line"> <span class="keyword">if</span> len(args) == <span class="number">1</span> <span class="keyword">and</span> len(kwargs) == <span class="number">0</span> <span class="keyword">and</span> callable(args[<span class="number">0</span>]):</span><br><span class="line"> <span class="comment"># 不带参数的 @ray.remote 装饰</span></span><br><span class="line"> <span class="keyword">return</span> make_remote_decorator(</span><br><span class="line"> num_return_vals, num_cpus, num_gpus, resources,</span><br><span class="line"> max_calls, checkpoint_interval)(args[<span class="number">0</span>])</span><br><span class="line"> <span class="keyword">else</span>:</span><br><span class="line"> <span class="comment"># 带参数的 @ray.remote(xx=x) 装饰</span></span><br><span class="line"> ...</span><br><span class="line"> <span class="keyword">return</span> make_remote_decorator(num_return_vals, num_cpus, num_gpus,</span><br><span class="line"> resources, max_calls, checkpoint_interval)</span><br></pre></td></tr></table></figure></p>
<p>【先验知识:Python装饰器的概念】<br>由此可见,<code>remote</code>是一个通用的装饰器,可以装饰普通的Python函数,或者是Python的class。</p>
<p>进入<code>remote</code>装饰器体,首先得到全局Worker,然后定义了一个<code>make_remote_decorator</code>装饰器工厂函数,然后判断是无参装饰还是带参装饰。<br>如果是无参装饰,那么<br><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@ray.remote</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">f</span><span class="params">()</span>:</span></span><br><span class="line"> time.sleep(<span class="number">1</span>)</span><br><span class="line"> <span class="keyword">return</span> <span class="number">1</span></span><br></pre></td></tr></table></figure></p>
<p>等价于<br><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">f</span><span class="params">()</span>:</span></span><br><span class="line"> ...</span><br><span class="line"> </span><br><span class="line">f = ray.remote(f)</span><br></pre></td></tr></table></figure></p>
<p>此时,remote的参数只有一个,那就是<code>f</code>本身,也即<code>args[0]</code>。<br>所以上述代码返回<code>make_remote_decorator(...)(args[0])</code>,即调用过的<code>make_remote_decorator</code>,参数是<code>f</code>。</p>
<p>否则remote函数定义等价于:<br><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">f</span><span class="params">()</span>:</span></span><br><span class="line"> ...</span><br><span class="line"></span><br><span class="line">f = ray.remote(num_cpus=<span class="number">1</span>, ..)(f)</span><br></pre></td></tr></table></figure></p>
<p>所以<code>remote</code>装饰器返回一个未调用的,将会在<code>f</code>上调用的<code>make_remote_decorator</code>函数。</p>
<p><code>make_remote_decorator</code>中再嵌套了一层装饰,本身提供对函数和actor的区分。</p>
<p>如果是函数,则进入<code>remote_function_decorator</code>远程函数装饰器;<br>否则是class,由全局worker创建一个actor。</p>
<p>远程函数装饰器<code>remote_function_decorator</code>的责任就是接受函数参数,返回一个函数,这个函数就是远程函数,不能直接传参调用(第29行)。将<code>remote()</code>绑定到<code>func_call</code>,接受参数后,提交任务(<code>_submit_task</code>)运行这个函数,最后得到结果,这个结果也是<code>f.remote()</code>调用的结果,是一个<code>object id</code>,因为返回结果存在object store中。</p>
<p>至此,远程函数就定义好了。我们在原始的普通Python函数<code>f</code>上,装饰了一下,得到了一个可以通过<code>f.remote()</code>来调用的远程函数,如此调用将会立马提交一个任务,供Ray引擎调度执行,返回结果。</p>
<p>下面是<code>ray.init()</code>过程。可以理解为初始化Ray引擎的过程,类似于启动Tensorflow的Session的过程。</p>
<p><code>ray.init()</code>也有带参版本和无参版本。<br>带参版本用于已经存在并启动一个Ray集群的情况下,直接填入该集群的redis地址,即可连接到集群,就初始化好了。<br>无参版本适用于单机多进程的运行,这种情况下会创建一个Ray环境,默认启动一个local scheduler,一个global scheduler,一个或多个redis server, 一个object store和一个object store manager,和若干worker进程(默认为CPU核数个)。</p>
<p><code>init()</code>主要逻辑为:<br><figure class="highlight nix"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># ray/python/ray/worker.py</span></span><br><span class="line">init()</span><br><span class="line"> _init()</span><br><span class="line"> <span class="keyword">if</span> PYTHON_MODE:</span><br><span class="line"> pass</span><br><span class="line"> elif start_ray_local: <span class="comment"># 本地开启一个Ray主节点进程</span></span><br><span class="line"> <span class="attr">address_info</span> = services.start_ray_head(..)</span><br><span class="line"> <span class="keyword">else</span>: <span class="comment"># 连接到已有集群</span></span><br><span class="line"> <span class="attr">address_info</span> = get_address_info_from_redis(redis_address, node_ip_address)</span><br><span class="line"> <span class="comment"># 将全局worker连接到 local scheduler, Plasma 和 Redis</span></span><br><span class="line"> connect(driver_address_info, <span class="attr">object_id_seed=object_id_seed,</span></span><br><span class="line"> <span class="attr">mode=driver_mode,</span> <span class="attr">worker=global_worker)</span></span><br></pre></td></tr></table></figure></p>
<p>四个模式:<br><figure class="highlight sqf"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">SCRIPT_MODE:如果Worker是<span class="built_in">driver</span>,且由Python脚本启动或者在shell中交互式运行的话,使用脚本模式。会打印任务失败信息。</span><br><span class="line">WORKER_MODE:如果Worker不是<span class="built_in">driver</span>,只是slave的话,启动WORKER_MODE,不打印关于task的任何信息。</span><br><span class="line">PYTHON_MODE:如果要顺序运行或是调试,可以使用PYTHON_MODE,此时的Worker即是<span class="built_in">driver</span>。此模式下,不会发送remote函数到调度器,而是直接以阻塞的形式执行。</span><br><span class="line">SILENT_MODE:测试的时候使用SILENT_MODE。不会打印error信息,因为许多测试时故意失败的。</span><br></pre></td></tr></table></figure></p>
<p>我们的示例代码中,<code>ray.init()</code>是无参的,代表我们会在本地开启一个ray head节点进程。<br>此部分代码简要逻辑如下:<br><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># ray/python/ray/services.py</span></span><br><span class="line">start_ray_head</span><br><span class="line"> | start_ray_processes</span><br><span class="line"> | <span class="built_in">print</span>(<span class="string">"Process STDOUT and STDERR is being redirected to /tmp/raylogs/."</span>) <span class="comment"># 程序输出中第一行的来源</span></span><br><span class="line"> | <span class="keyword">if</span> redis_address is None:</span><br><span class="line"> | start_redis(...)</span><br><span class="line"> | start_redis_instance(..)</span><br><span class="line"> | 创建redis_shards个redis server</span><br><span class="line"> <span class="comment"># 等待redis server可用并响应,程序输出第2,3行的来源</span></span><br><span class="line"> | <span class="built_in">wait</span>_<span class="keyword">for</span>_redis_to_start(<span class="string">"127.0.0.1"</span>, port) </span><br><span class="line"> | <span class="keyword">if</span> include_<span class="built_in">log</span>_monitor:</span><br><span class="line"> | start_<span class="built_in">log</span>_monitor(..)</span><br><span class="line"> | <span class="keyword">if</span> include_global_scheduler:</span><br><span class="line"> | start_global_scheduler(...)</span><br><span class="line"> <span class="comment"># 开启local_scheduler并打印 Starting local scheduler ..,程序第4行的来源</span></span><br><span class="line"> | <span class="built_in">local</span>_scheduler_name, pid = ray.local_scheduler.start_<span class="built_in">local</span>_scheduler(...)</span><br><span class="line"> | <span class="keyword">for</span> i <span class="keyword">in</span> range(num_<span class="built_in">local</span>_schedulers - len(object_store_addresses)):</span><br><span class="line"> | start_objstore(...)</span><br><span class="line"> | ray.plasma.start_plasma_store(..)</span><br><span class="line"> | ray.plasma.start_plasma_manager(..)</span><br><span class="line"> | <span class="keyword">for</span> i <span class="keyword">in</span> range(len(<span class="built_in">local</span>_scheduler_socket_names), num_<span class="built_in">local</span>_schedulers):</span><br><span class="line"> | start_<span class="built_in">local</span>_scheduler(...)</span><br><span class="line"> <span class="comment"># 每个local scheduler默认搭配CPU核数个workers,因此workers_per_local_scheduler[i] = #cpus</span></span><br><span class="line"> | <span class="keyword">for</span> i, num_<span class="built_in">local</span>_scheduler_workers <span class="keyword">in</span> enumerate(workers_per_<span class="built_in">local</span>_scheduler):</span><br><span class="line"> | <span class="keyword">for</span> j <span class="keyword">in</span> range(num_<span class="built_in">local</span>_scheduler_workers):</span><br><span class="line"> | start_worker(...)</span><br><span class="line"> | <span class="keyword">if</span> include_webui:</span><br><span class="line"> | start_ui(...)</span><br><span class="line"> <span class="comment"># 开启UI会打印输出中UI的部分</span></span><br></pre></td></tr></table></figure></p>
<p>可以看到,<code>start_ray_head</code>的过程配套启动了redis, global scheduler, local scheduler及其workers,UI等。<br>这些都是ray执行快速的分布式任务分发的基本组件,其中redis用来存储全局系统状态,global scheduler和local scheduler分数两级调度器,负责快速的任务调度,workers负责执行远程函数,UI负责观察运行状态,不过目前UI做的还比较简陋。</p>
<p>每个Worker执行一个主循环<code>main_loop</code>,循环不断地接受任务,处理任务返回……<br>这部分代码见<code>ray/python/ray/workers/default_worker.py</code>。<br><code>main_loop</code>的代码如下:<br><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># ray/python/ray/worker.py</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">main_loop</span><span class="params">(self)</span>:</span></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">exit</span><span class="params">(signum, frame)</span>:</span></span><br><span class="line"> cleanup(worker=self)</span><br><span class="line"> sys.exit(<span class="number">0</span>)</span><br><span class="line"> signal.signal(signal.SIGTERM, exit)</span><br><span class="line"> check_main_thread()</span><br><span class="line"> <span class="keyword">while</span> <span class="keyword">True</span>:</span><br><span class="line"> <span class="comment"># 此处调用self.local_scheduler_client.get_task()获得任务</span></span><br><span class="line"> task = self._get_next_task_from_local_scheduler()</span><br><span class="line"> self._wait_for_and_process_task(task)</span><br><span class="line"> | self._wait_for_function(function_id, task.driver_id().id())</span><br><span class="line"> | <span class="keyword">with</span> self.lock:</span><br><span class="line"> | self._process_task(task)</span><br></pre></td></tr></table></figure></p>
<p>初始化好以后,就可以运行<code>f.remote()</code>了,运行后还是回到装饰器里面的<code>_submit_task</code>函数,<br><figure class="highlight nimrod"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># ray/python/ray/worker.py</span></span><br><span class="line">def _submit(args=<span class="type">None</span>, kwargs=<span class="type">None</span>, num_return_vals=<span class="type">None</span>,</span><br><span class="line"> num_cpus=<span class="type">None</span>, num_gpus=<span class="type">None</span>, resources=<span class="type">None</span>):</span><br><span class="line"> check_connected() <span class="comment"># 检查worker是否连接</span></span><br><span class="line"> check_main_thread() <span class="comment"># 检查是否主线程,不允许非主线程提交任务</span></span><br><span class="line"> kwargs = {} <span class="keyword">if</span> kwargs <span class="keyword">is</span> <span class="type">None</span> <span class="keyword">else</span> kwargs</span><br><span class="line"> args = signature.extend_args(function_signature, args, kwargs)</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> _mode() == <span class="type">PYTHON_MODE</span>:</span><br><span class="line"> <span class="comment"># PYTHON模式下,并不提交任务,而是串行执行,拷贝参数以防修改</span></span><br><span class="line"> <span class="literal">result</span> = func(*copy.deepcopy(args))</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">result</span></span><br><span class="line"> <span class="comment"># 提交任务,返回结果的object id或者一组object ids</span></span><br><span class="line"> object_ids = _submit_task(function_id, args,</span><br><span class="line"> num_return_vals=num_return_vals,</span><br><span class="line"> num_cpus=num_cpus, num_gpus=num_gpus,</span><br><span class="line"> resources=resources)</span><br><span class="line"> <span class="keyword">if</span> len(object_ids) == <span class="number">1</span>:</span><br><span class="line"> <span class="keyword">return</span> object_ids[<span class="number">0</span>]</span><br><span class="line"> <span class="keyword">elif</span> len(object_ids) > <span class="number">1</span>:</span><br><span class="line"> <span class="keyword">return</span> object_ids</span><br></pre></td></tr></table></figure></p>
<p>代码中调用的<code>_submit_task</code>是对<code>worker.submit_task</code>的一个封装:<br><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># ray/python/ray/worker.py</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">_submit_task</span><span class="params">(function_id, *args, **kwargs)</span>:</span></span><br><span class="line"> <span class="string">"""This is a wrapper around worker.submit_task.</span><br><span class="line"></span><br><span class="line"> We use this wrapper so that in the remote decorator, we can call</span><br><span class="line"> _submit_task instead of worker.submit_task. The difference is that when we</span><br><span class="line"> attempt to serialize remote functions, we don't attempt to serialize the</span><br><span class="line"> worker object, which cannot be serialized. 【这样搞一下就不需要序列化worker对象了?】</span><br><span class="line"> """</span></span><br><span class="line"> <span class="keyword">return</span> global_worker.submit_task(function_id, *args, **kwargs)</span><br></pre></td></tr></table></figure></p>
<p>最终,Worker的<code>submit_task</code>函数如下:<br><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># ray/python/ray/worker.py</span></span><br><span class="line">def submit_task(self, <span class="keyword">function</span>_id, args, ...):</span><br><span class="line"> with <span class="built_in">log</span>_span(<span class="string">"ray:submit_task"</span>, worker=self):</span><br><span class="line"> check_main_thread()</span><br><span class="line"> ...</span><br><span class="line"> <span class="comment"># 将参数put进object store,注意,如果多个函数使用的是相同的输入,直接调用的话仍然会put多次</span></span><br><span class="line"> <span class="comment"># 一个方法是先在调用前put参数,然后传入put后的ObjectID对象。</span></span><br><span class="line"> args_<span class="keyword">for</span>_<span class="built_in">local</span>_scheduler = []</span><br><span class="line"> <span class="keyword">for</span> arg <span class="keyword">in</span> args:</span><br><span class="line"> <span class="keyword">if</span> isinstance(arg, ray.local_scheduler.ObjectID):</span><br><span class="line"> args_<span class="keyword">for</span>_<span class="built_in">local</span>_scheduler.append(arg)</span><br><span class="line"> <span class="keyword">elif</span> isinstance(arg, ray.actor.ActorHandleParent):</span><br><span class="line"> args_<span class="keyword">for</span>_<span class="built_in">local</span>_scheduler.append(put(</span><br><span class="line"> ray.actor.wrap_actor_handle(arg)))</span><br><span class="line"> <span class="keyword">elif</span> ray.local_scheduler.check_simple_value(arg):</span><br><span class="line"> args_<span class="keyword">for</span>_<span class="built_in">local</span>_scheduler.append(arg)</span><br><span class="line"> <span class="keyword">else</span>:</span><br><span class="line"> args_<span class="keyword">for</span>_<span class="built_in">local</span>_scheduler.append(put(arg))</span><br><span class="line"> ...</span><br><span class="line"> <span class="comment"># Submit the task to local scheduler.</span></span><br><span class="line"> task = ray.local_scheduler.Task(</span><br><span class="line"> self.task_driver_id,</span><br><span class="line"> ray.local_scheduler.ObjectID(<span class="keyword">function</span>_id.id()),</span><br><span class="line"> args_<span class="keyword">for</span>_<span class="built_in">local</span>_scheduler,</span><br><span class="line"> ...)</span><br><span class="line"> ...</span><br><span class="line"> self.task_index += 1</span><br><span class="line"> self.local_scheduler_client.submit(task)</span><br><span class="line"></span><br><span class="line"> <span class="built_in">return</span> task.returns()</span><br></pre></td></tr></table></figure></p>
<p>也就是说,<code>[f.remote() for i in range(4)]</code>这一句,默认的全局worker会提交4个任务给local scheduler,然后local scheduler将这些任务调度到嗷嗷待哺的各个worker,前面的代码说过,默认会启动CPU核数个Worker。</p>
<p>运行完毕后,列表中就是返回的值的object id,我们需要使用<code>ray.get(id)</code>从object store中将真正的数据拿出来。</p>
<p>最后就成了<code>[1, 1, 1, 1]</code>,程序到此就结束了。</p>
<p>再回顾一下整个过程:<br><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@ray.remote # 装饰器</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">f</span><span class="params">()</span>:</span></span><br><span class="line"> time.sleep(<span class="number">1</span>)</span><br><span class="line"> <span class="keyword">return</span> <span class="number">1</span></span><br><span class="line"> <span class="comment"># 装饰完成,装饰过后的远程函数f已形成</span></span><br><span class="line">ray.init() <span class="comment"># 初始化Ray引擎,会启动各个必要组件,包括调度,状态存储,对象存储和workers等</span></span><br><span class="line">results = ray.get([f.remote() <span class="keyword">for</span> i <span class="keyword">in</span> range(<span class="number">4</span>)]) <span class="comment"># 提交任务,获得结果,从object store中取出</span></span><br><span class="line">print(results)</span><br></pre></td></tr></table></figure></p>
<p>Happy Reading!</p>
<p>Ray Version 0.4.0</p>
<p>以一个Ray示例程序来说明,Ray执行多进程/分布式程序的过程。<br><figure class="highlight python"><table><tr><td class="gutter"><pre><span cl
Linux异常集锦
http://whatbeg.com/2019/04/16/linuxexceptions.html
2019-04-16T12:50:04.000Z
2019-04-16T12:52:34.135Z
<p>新开一贴,记录一下Linux使用中的一些问题。</p>
<h5 id="ssh不通,并报如下错误"><a href="#ssh不通,并报如下错误" class="headerlink" title="ssh不通,并报如下错误"></a>ssh不通,并报如下错误</h5><figure class="highlight gauss"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">@@</span><span class="comment">@@</span><span class="comment">@@</span><span class="comment">@@</span><span class="comment">@@</span><span class="comment">@@</span><span class="comment">@@</span><span class="comment">@@</span><span class="comment">@@</span><span class="comment">@@</span><span class="comment">@@</span><span class="comment">@@</span><span class="comment">@@</span><span class="comment">@@</span><span class="comment">@@</span><span class="comment">@@</span><span class="comment">@@</span><span class="comment">@@</span><span class="comment">@@</span><span class="comment">@@</span><span class="comment">@@</span><span class="comment">@@</span><span class="comment">@@</span><span class="comment">@@</span><span class="comment">@@</span><span class="comment">@@</span><span class="comment">@@</span><span class="comment">@@</span><span class="comment">@@</span><span class="comment">@</span><br><span class="line">@</span> WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! <span class="comment">@</span><br><span class="line">@</span><span class="comment">@@</span><span class="comment">@@</span><span class="comment">@@</span><span class="comment">@@</span><span class="comment">@@</span><span class="comment">@@</span><span class="comment">@@</span><span class="comment">@@</span><span class="comment">@@</span><span class="comment">@@</span><span class="comment">@@</span><span class="comment">@@</span><span class="comment">@@</span><span class="comment">@@</span><span class="comment">@@</span><span class="comment">@@</span><span class="comment">@@</span><span class="comment">@@</span><span class="comment">@@</span><span class="comment">@@</span><span class="comment">@@</span><span class="comment">@@</span><span class="comment">@@</span><span class="comment">@@</span><span class="comment">@@</span><span class="comment">@@</span><span class="comment">@@</span><span class="comment">@@</span><span class="comment">@@</span></span><br><span class="line">IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!</span><br><span class="line">Someone could be eavesdropping on you right now (man-in-the-middle attack)!</span><br><span class="line">It is also possible that a host <span class="built_in">key</span> has just been changed.</span><br><span class="line">The fingerprint <span class="keyword">for</span> the ECDSA <span class="built_in">key</span> sent by the remote host is</span><br><span class="line">a9:<span class="number">31</span>:<span class="number">72</span>:<span class="number">45</span>:<span class="number">8</span>d:fb:<span class="number">13</span>:<span class="number">57</span>:<span class="number">85</span>:cc:<span class="number">0</span>a:<span class="number">1</span>c:<span class="number">56</span>:<span class="number">33</span>:c6:<span class="number">04.</span></span><br><span class="line">Please contact your <span class="keyword">system</span> administrator.</span><br><span class="line">Add correct host <span class="built_in">key</span> in /home/experiment/.ssh/known_hosts to get rid of this message.</span><br><span class="line">Offending ECDSA <span class="built_in">key</span> in /home/experiment/.ssh/known_hosts:<span class="number">14</span></span><br><span class="line">ECDSA host <span class="built_in">key</span> <span class="keyword">for</span> slave103 has changed <span class="keyword">and</span> you have requested strict checking.</span><br><span class="line">Host <span class="built_in">key</span> verification failed.</span><br></pre></td></tr></table></figure>
<p>【解决方案】将<code>/home/experiment/.ssh/known_hosts</code>对应行删掉。因为可能host key已经更新了。</p>
<h5 id="scp时,-bash-syntax-error-near-unexpected-token-‘-‘"><a href="#scp时,-bash-syntax-error-near-unexpected-token-‘-‘" class="headerlink" title="scp时,-bash: syntax error near unexpected token ‘(‘"></a>scp时,-bash: syntax error near unexpected token ‘(‘</h5><p>scp/rsync远程传输时,如果文件名包含括号’(‘或’)’,则简单的传输会报错,如下:<br><figure class="highlight groovy"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ scp <span class="string">remote01:</span>.<span class="regexp">/xx/</span>haha(<span class="number">2019</span>).lcv .</span><br><span class="line">-<span class="string">bash:</span> syntax error near unexpected token `(<span class="string">'</span></span><br></pre></td></tr></table></figure></p>
<p>如果简单加上转义符’\’或者引号’’,也会报错:<br><figure class="highlight dts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">$ scp remote01:.<span class="meta-keyword">/xx/</span>haha\(<span class="number">2019</span>\).lcv .</span><br><span class="line">或者</span><br><span class="line">$ scp remote01:.<span class="meta-keyword">/xx/</span>haha<span class="string">"(2019)"</span>.lcv .</span><br><span class="line"><span class="symbol"></span><br><span class="line">bash:</span> -c: line <span class="number">0</span>: syntax error near unexpected token `('</span><br><span class="line"><span class="symbol">bash:</span> -c: line <span class="number">0</span>: `scp -f .<span class="meta-keyword">/huqiu/</span>das<span class="meta-keyword">/benchmarks/</span>lcvs/slave028_aeh_adult_r0_trial_500_P_100_S_1_TL_360_04061553(<span class="number">0.874701</span>).lcv'</span><br></pre></td></tr></table></figure></p>
<p>这时候,其实是加少了,因为涉及到本地端和remote端,所以需要提供可供两次转义还正确的写法。</p>
<p>【解决方案】<br><figure class="highlight tex"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="formula">$ scp 'remote01:./xx/haha<span class="tag">\<span class="name">(</span></span>2019<span class="tag">\<span class="name">)</span></span>.lcv' .</span><br><span class="line">或者</span><br><span class="line">$</span> scp remote01:./xx/haha"<span class="tag">\<span class="name">(</span></span>2019<span class="tag">\<span class="name">)</span></span>".lcv .</span><br><span class="line">或者</span><br><span class="line"><span class="formula">$ scp remote01:./xx/haha<span class="tag">\<span class="name">\</span></span><span class="tag">\<span class="name">(</span></span>2019<span class="tag">\<span class="name">\</span></span><span class="tag">\<span class="name">)</span></span>.lcv .</span></span><br></pre></td></tr></table></figure></p>
<p>新开一贴,记录一下Linux使用中的一些问题。</p>
<h5 id="ssh不通,并报如下错误"><a href="#ssh不通,并报如下错误" class="headerlink" title="ssh不通,并报如下错误"></a>ssh不通,并报如下错误</h5><f
近代史年表(1839-1911)
http://whatbeg.com/2019/04/16/modernhistorylist.html
2019-04-16T12:38:28.000Z
2019-04-16T13:18:39.940Z
<table>
<thead>
<tr>
<th style="text-align:center">公元年份</th>
<th style="text-align:center">朝代年号</th>
<th style="text-align:center">大事记</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:center">1839</td>
<td style="text-align:center">道光十九年</td>
<td style="text-align:center">林则徐于虎门销毁鸦片。</td>
</tr>
<tr>
<td style="text-align:center">1840</td>
<td style="text-align:center">道光二十年</td>
<td style="text-align:center">中英第一次鸦片战争爆发(1840~1842年)。</td>
</tr>
<tr>
<td style="text-align:center">1841</td>
<td style="text-align:center">道光二十一年</td>
<td style="text-align:center"></td>
</tr>
<tr>
<td style="text-align:center">1842</td>
<td style="text-align:center">道光二十二年</td>
<td style="text-align:center">中英签订《南京条约》,英占香港岛,开放五口通商。</td>
</tr>
<tr>
<td style="text-align:center">1843</td>
<td style="text-align:center">道光二十三年</td>
<td style="text-align:center">中英签订《五口通商章程》,《虎门条约》。同年,洪秀全创立拜上帝会。</td>
</tr>
<tr>
<td style="text-align:center">1844</td>
<td style="text-align:center">道光二十四年</td>
<td style="text-align:center">中美《望厦条约》、中法《黄埔条约》订立。</td>
</tr>
<tr>
<td style="text-align:center">1845</td>
<td style="text-align:center">道光二十五年</td>
<td style="text-align:center"></td>
</tr>
<tr>
<td style="text-align:center">1846</td>
<td style="text-align:center">道光二十六年</td>
<td style="text-align:center"></td>
</tr>
<tr>
<td style="text-align:center">1847</td>
<td style="text-align:center">道光二十七年</td>
<td style="text-align:center"></td>
</tr>
<tr>
<td style="text-align:center">1848</td>
<td style="text-align:center">道光二十八年</td>
<td style="text-align:center"></td>
</tr>
<tr>
<td style="text-align:center">1849</td>
<td style="text-align:center">道光二十九年</td>
<td style="text-align:center"></td>
</tr>
<tr>
<td style="text-align:center">1850</td>
<td style="text-align:center">道光三十年</td>
<td style="text-align:center"></td>
</tr>
<tr>
<td style="text-align:center">1851</td>
<td style="text-align:center">咸丰元年</td>
<td style="text-align:center">清咸丰元年,清朝人口达43610万。1月,拜上帝会在广西金田村起事,建号太平天国。</td>
</tr>
<tr>
<td style="text-align:center">1852</td>
<td style="text-align:center">咸丰二年</td>
<td style="text-align:center"></td>
</tr>
<tr>
<td style="text-align:center">1853</td>
<td style="text-align:center">咸丰三年</td>
<td style="text-align:center">太平军攻入南京,改名天京,定为国都;并颁《天朝田亩制度》。</td>
</tr>
<tr>
<td style="text-align:center">1854</td>
<td style="text-align:center">咸丰四年</td>
<td style="text-align:center"></td>
</tr>
<tr>
<td style="text-align:center">1855</td>
<td style="text-align:center">咸丰五年</td>
<td style="text-align:center">5月,僧格林沁攻陷冯官屯,俘李开芳,太平天国北伐军失败。</td>
</tr>
<tr>
<td style="text-align:center">1856</td>
<td style="text-align:center">咸丰六年</td>
<td style="text-align:center">第二次鸦片战争(1856-4860)爆发。英法联军侵华。同年,天京事变。太平天国内讧,渐趋败亡。</td>
</tr>
<tr>
<td style="text-align:center">1857</td>
<td style="text-align:center">咸丰七年</td>
<td style="text-align:center"></td>
</tr>
<tr>
<td style="text-align:center">1858</td>
<td style="text-align:center">咸丰八年</td>
<td style="text-align:center">英法联军攻陷大沽,清廷与两国签订《天津条约》,又与俄签订《瑷珲条约》。</td>
</tr>
<tr>
<td style="text-align:center">1859</td>
<td style="text-align:center">咸丰九年</td>
<td style="text-align:center">英法联军再次入侵中国。</td>
</tr>
<tr>
<td style="text-align:center">1860</td>
<td style="text-align:center">咸丰十年</td>
<td style="text-align:center">英法联军火烧圆明园;攻陷北京。中英、中法、中我分别签订《北京条约》。12月,白凌阿等于义州起事,东北各族人民大起义爆发。</td>
</tr>
<tr>
<td style="text-align:center">1861</td>
<td style="text-align:center">咸丰十一年</td>
<td style="text-align:center">8月咸丰在热河驾崩。11月1日,辛酉政变,慈禧太后登上中国政治舞台。同年,洋务运动(1861~1894年)开始,创办军事工业、实业,编练陆海军设西式学堂。3月,白凌阿等部起义军攻破朝阳、赤峰县城。</td>
</tr>
<tr>
<td style="text-align:center">1862</td>
<td style="text-align:center">同治元年</td>
<td style="text-align:center"></td>
</tr>
<tr>
<td style="text-align:center">1863</td>
<td style="text-align:center">同治二年</td>
<td style="text-align:center"></td>
</tr>
<tr>
<td style="text-align:center">1864</td>
<td style="text-align:center">同治三年</td>
<td style="text-align:center">洪秀全病死,清军攻入南京,太平天国败亡。同年,中俄签订《勘分西北界约记》。</td>
</tr>
<tr>
<td style="text-align:center">1865</td>
<td style="text-align:center">同治四年</td>
<td style="text-align:center"></td>
</tr>
<tr>
<td style="text-align:center">1866</td>
<td style="text-align:center">同治五年</td>
<td style="text-align:center"></td>
</tr>
<tr>
<td style="text-align:center">1867</td>
<td style="text-align:center">同治六年</td>
<td style="text-align:center"></td>
</tr>
<tr>
<td style="text-align:center">1868</td>
<td style="text-align:center">同治七年</td>
<td style="text-align:center">2月,白凌河被清军逮捕杀害。</td>
</tr>
<tr>
<td style="text-align:center">1869</td>
<td style="text-align:center">同治八年</td>
<td style="text-align:center">1月,弥勒僧格被清军逮捕杀害,东北各族人民大起义失败。</td>
</tr>
<tr>
<td style="text-align:center">1870</td>
<td style="text-align:center">同治九年</td>
<td style="text-align:center"></td>
</tr>
<tr>
<td style="text-align:center">1871</td>
<td style="text-align:center">同治十年</td>
<td style="text-align:center">7月,俄国侵略军强占伊犁。</td>
</tr>
<tr>
<td style="text-align:center">1872</td>
<td style="text-align:center">同治十一年</td>
<td style="text-align:center"></td>
</tr>
<tr>
<td style="text-align:center">1873</td>
<td style="text-align:center">同治十二年</td>
<td style="text-align:center"></td>
</tr>
<tr>
<td style="text-align:center">1874</td>
<td style="text-align:center">同治十三年</td>
<td style="text-align:center"></td>
</tr>
<tr>
<td style="text-align:center">1875</td>
<td style="text-align:center">光绪元年</td>
<td style="text-align:center"></td>
</tr>
<tr>
<td style="text-align:center">1876</td>
<td style="text-align:center">光绪二年</td>
<td style="text-align:center"></td>
</tr>
<tr>
<td style="text-align:center">1877</td>
<td style="text-align:center">光绪三年</td>
<td style="text-align:center"></td>
</tr>
<tr>
<td style="text-align:center">1878</td>
<td style="text-align:center">光绪四年</td>
<td style="text-align:center"></td>
</tr>
<tr>
<td style="text-align:center">1879</td>
<td style="text-align:center">光绪五年</td>
<td style="text-align:center"></td>
</tr>
<tr>
<td style="text-align:center">1880</td>
<td style="text-align:center">光绪六年</td>
<td style="text-align:center">张之洞奏陈《详筹边计折》。</td>
</tr>
<tr>
<td style="text-align:center">1881</td>
<td style="text-align:center">光绪七年</td>
<td style="text-align:center"></td>
</tr>
<tr>
<td style="text-align:center">1882</td>
<td style="text-align:center">光绪八年</td>
<td style="text-align:center"></td>
</tr>
<tr>
<td style="text-align:center">1883</td>
<td style="text-align:center">光绪九年</td>
<td style="text-align:center">中法战争(1883~1885年)爆发。</td>
</tr>
<tr>
<td style="text-align:center">1884</td>
<td style="text-align:center">光绪十年</td>
<td style="text-align:center"></td>
</tr>
<tr>
<td style="text-align:center">1885</td>
<td style="text-align:center">光绪十一年</td>
<td style="text-align:center">中法签订《越南条约》,法占领越南。</td>
</tr>
<tr>
<td style="text-align:center">1886</td>
<td style="text-align:center">光绪十二年</td>
<td style="text-align:center"></td>
</tr>
<tr>
<td style="text-align:center">1887</td>
<td style="text-align:center">光绪十三年</td>
<td style="text-align:center"></td>
</tr>
<tr>
<td style="text-align:center">1888</td>
<td style="text-align:center">光绪十四年</td>
<td style="text-align:center">清廷建立北洋水师,加强军备,巩固海疆。</td>
</tr>
<tr>
<td style="text-align:center">1889</td>
<td style="text-align:center">光绪十五年</td>
<td style="text-align:center"></td>
</tr>
<tr>
<td style="text-align:center">1890</td>
<td style="text-align:center">光绪十六年</td>
<td style="text-align:center"></td>
</tr>
<tr>
<td style="text-align:center">1891</td>
<td style="text-align:center">光绪十七年</td>
<td style="text-align:center"></td>
</tr>
<tr>
<td style="text-align:center">1892</td>
<td style="text-align:center">光绪十八年</td>
<td style="text-align:center"></td>
</tr>
<tr>
<td style="text-align:center">1893</td>
<td style="text-align:center">光绪十九年</td>
<td style="text-align:center"></td>
</tr>
<tr>
<td style="text-align:center">1894</td>
<td style="text-align:center">光绪二十年</td>
<td style="text-align:center">中日甲午战争(1894~1895年)爆发。同年,孙中山在檀香山创立兴中会。</td>
</tr>
<tr>
<td style="text-align:center">1895</td>
<td style="text-align:center">光绪二十一年</td>
<td style="text-align:center">中日签订《马关条约》,割让台湾及辽东半岛。俄法德三国干涉还辽。同年,洋务运动宣告终结。</td>
</tr>
<tr>
<td style="text-align:center">1896</td>
<td style="text-align:center">光绪二十二年</td>
<td style="text-align:center">《中俄密约》签订;此后列强纷纷在华租借港湾,划分势力范围。</td>
</tr>
<tr>
<td style="text-align:center">1897</td>
<td style="text-align:center">光绪二十三年</td>
<td style="text-align:center">德国强租胶州湾;沙俄占旅顺及大连。</td>
</tr>
<tr>
<td style="text-align:center">1898</td>
<td style="text-align:center">光绪二十四年</td>
<td style="text-align:center">6月,光绪帝在康有为等推动宣布“戊戌变法”,同年9月,慈禧发动政变,变法失败,又称“百日维新”。</td>
</tr>
<tr>
<td style="text-align:center">1899</td>
<td style="text-align:center">光绪二十五年</td>
<td style="text-align:center">义和团兴起,在山东各地杀教士、教民。</td>
</tr>
<tr>
<td style="text-align:center">1900</td>
<td style="text-align:center">光绪二十六年</td>
<td style="text-align:center">6月21日,慈禧对全世界宣战。8月16日,八国联军攻陷北京。同年,兴中会惠州起义失败。</td>
</tr>
<tr>
<td style="text-align:center">1901</td>
<td style="text-align:center">光绪二十七年</td>
<td style="text-align:center">清政府和西方列强十一国签订《辛丑条约》。清廷下令筹划新政。</td>
</tr>
<tr>
<td style="text-align:center">1902</td>
<td style="text-align:center">光绪二十八年</td>
<td style="text-align:center"></td>
</tr>
<tr>
<td style="text-align:center">1903</td>
<td style="text-align:center">光绪二十九年</td>
<td style="text-align:center"></td>
</tr>
<tr>
<td style="text-align:center">1904</td>
<td style="text-align:center">光绪三十年</td>
<td style="text-align:center"></td>
</tr>
<tr>
<td style="text-align:center">1905</td>
<td style="text-align:center">光绪三十一年</td>
<td style="text-align:center">清政府罢科举,派五大臣出洋考察宪政。同年,孙中山创立中国同盟会,提出三民主义。</td>
</tr>
<tr>
<td style="text-align:center">1906</td>
<td style="text-align:center">光绪三十二年</td>
<td style="text-align:center">清政府宣布「预备立宪」。</td>
</tr>
<tr>
<td style="text-align:center">1907</td>
<td style="text-align:center">光绪三十三年</td>
<td style="text-align:center"></td>
</tr>
<tr>
<td style="text-align:center">1908</td>
<td style="text-align:center">光绪三十四年</td>
<td style="text-align:center">光绪帝、慈禧太后先后驾崩;宣统帝即位。</td>
</tr>
<tr>
<td style="text-align:center">1909</td>
<td style="text-align:center">宣统元年</td>
<td style="text-align:center"></td>
</tr>
<tr>
<td style="text-align:center">1910</td>
<td style="text-align:center">宣统二年</td>
<td style="text-align:center"></td>
</tr>
<tr>
<td style="text-align:center">1911</td>
<td style="text-align:center">宣统三年</td>
<td style="text-align:center">10月10日,武昌起义爆发。中华民国诞生。</td>
</tr>
</tbody>
</table>
<table>
<thead>
<tr>
<th style="text-align:center">公元年份</th>
<th style="text-align:center">朝代年号</th>
<th style="text-align:center">大事记</th>
C++面向对象FQA
http://whatbeg.com/2019/04/16/cppfqa.html
2019-04-16T12:35:43.000Z
2019-04-16T12:36:45.504Z
<p><strong>Q. 面向对象的理解?</strong></p>
<p>面向对象是一种程序设计方法。面向对象有三大特性:封装,继承,多态。</p>
<p>1) 封装:</p>
<blockquote>
<p>封装可以隐藏实现细节,使得代码模块化;封装是把过程和数据包围起来,对数据的访问只能通过已定义的界面。面向对象计算始于这个基本概念,即现实世界可以被描绘成一系列完全自治、封装的对象,这些对象通过一个受保护的接口访问其他对象。在面向对象编程上可理解为:把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。</p>
</blockquote>
<p>2) 继承:</p>
<blockquote>
<p>继承是指这样一种能力:它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。其继承的过程,就是从一般到特殊的过程。<br>通过继承创建的新类称为“子类”或“派生类”。被继承的类称为“基类”、“父类”或“超类”。要实现继承,可以通过“继承”(Inheritance)和“组合”(Composition)来实现。在某些<br>OOP 语言中,一个子类可以继承多个基类。但是一般情况下,一个子类只能有一个基类,要实现多重继承,可以通过多级继承来实现。</p>
</blockquote>
<p>3) 多态:</p>
<blockquote>
<p>多态性(polymorphisn)是允许你将父对象设置成为和一个或更多的他的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。简单的说,就是一句话:允许将子类类型的指针赋值给父类类型的指针。</p>
</blockquote>
<p>封装和继承比较简单,多态这块比较繁琐,多啰嗦一些。</p>
<p>说说多态的作用,多态在C++中其实分为静态多态和动态多态,静态多态其实就是重载,也是C语言没有的功能。</p>
<p>1)动态多态:多态的目的是为了接口重用。也就是说,不论传递过来的究竟是那个类的对象,函数都能够通过同一个接口调用到适应各自对象的实现方法。可以通过基类指针对所有派生类(包括直接派生和间接派生)的成员变量和成员函数进行“全方位”的访问,尤其是成员函数。如果没有多态,只能访问指针类型所在类的对象。</p>
<p>2)静态多态:通过函数重载来实现<br>a/ 类的构造函数和类名相同,如果没有重载,实例化不同对象非常麻烦<br>b/ 操作符重载,可以大大丰富操作符的含义,并且为特殊类指定特殊的运算符<br>c/ 可能有同一个函数需要处理不同的参数类型,重载后无需改变函数名字,也起到统一接口的作用</p>
<p>多态通过虚函数来实现。即父类定义虚函数,子类继承或重写虚函数。</p>
<p>虚函数的内存结构如下:</p>
<p>在一个类中,成员函数有虚函数的话,那么,这个对象的前四个字节是存放一个指向这个虚函数表(简称虚表)的指针。虚表里面放的是虚函数的地址。即使是存在虚基类指针,虚表指针也是在虚基类指针的上方,这是为了保证正确取到虚函数的偏移量。</p>
<p>C++也允许多继承,但多继承会有菱形继承问题,那么对于菱形继承,有多个基类的类对象,则会有多个虚表,每一个基类对应一个虚表,同时,虚表的顺序和继承时的顺序相同。但是菱形继承会造成数据冗余和二义性,虚继承可以解决菱形继承的数据冗余与二义性。</p>
<p><strong>Q. 什么函数不能声明为虚函数?</strong></p>
<p>1) 构造函数:因为先构造父类然后构造子类,所以父类必须有实构造函数<br>2) 静态成员函数:静态成员属于类,不属于对象,没有多态一说<br>3) 内联函数:内联函数涉及到展开,在编译器将函数类容替换到函数调用处,是静态编译的。而虚函数是动态调用的,在编译器并不知道需要调用的是父类还是子类的虚函数,所以不能够inline声明展开,所以编译器会忽略。</p>
<p>同时,析构函数可以调用虚函数,而构造函数不可以调用虚函数,理由在于,创建类对象时,会先构造父类子对象再构造该类自己,构建父类子对象的时候,如果构造函数包含虚函数,这个虚函数不会解析为子类重写过的函数,而是父类本身的函数。即,父类对象构造期间虚函数绝不会下降到子类层。从而引起非预期的结果。</p>
<p>在析构函数中,不仅可以调用虚函数,而且非常建议(甚至必须)在有虚函数的时候,创造一个虚析构函数。<br>因为如果对象经由父类指针被delete时,如果析构函数非虚,则可能只会析构父对象部分,子类成分很可能没被销毁!(C++官方解释是,未有定义)如果基类的析构函数是虚函数,则会在运行时多态的影响下调用派生类的析构函数。<br>所以,只要类有一个虚函数,都应该将析构函数声明为虚函数。</p>
<p>多态是有代价的,C++访问虚函数比访问普通函数慢,原因如下:</p>
<p>1)多了几条汇编指令(运行时得到对应类的函数的地址,取虚表,取虚函数地址,call调用)。<br>2)影响CPU流水线(这个没有展开,有兴趣的可以自己查一下)<br>3)编译器不能内联优化(仅在用父类引用或指针调用时,不能内联,是因为在得到子类的引用或者指针之前,根本不知道要调用哪个函数,所以无从内联,但是值得注意的是:对于子类直接调用虚函数,是可以内联优化的。)</p>
<p>再说说,构造,析构,拷贝构造等函数。</p>
<p>C++ 类中默认自带的6个函数<br>构造函数,析构函数,拷贝构造函数,重载赋值=运算符,取地址运算符,const修饰的取地址运算符</p>
<p>构造函数,拷贝构造函数,析构函数是子类不能继承父类的函数</p>
<p>异常经常会出现,那么在构造函数和析构函数中能否抛出异常呢?</p>
<p><strong>Q. 构造函数是否可以抛出异常?</strong></p>
<p>1、构造函数中抛出异常,对象的析构函数将不会被执行。<br>2、尽量不要让异常处理离开构造函数,应该再构造函数内部就地处理<br>3、构造函数抛出异常时,本应该在析构函数中被delete的对象没有被delete,会导致内存泄露。</p>
<p><strong>Q. 析构函数是否可以抛出异常?</strong></p>
<p>1、 不要在析构函数中抛出异常!虽然C++并不禁止析构函数抛出异常,但这样会导致程序过早结束或出现不明确的行为。<br>2、 如果某个操作可能会抛出异常,class应提供一个普通函数(而非析构函数),来执行该操作。目的是给客户一个处理错误的机会。<br>3、 如果析构函数中异常非抛不可,那就用try catch来将异常吞下,但这样方法并不好,我们提倡有错早些报出来。<br>总结<br>1、 构造函数中尽量不要抛出异常,能避免的就避免,如果必须,要考虑不要内存泄露!<br>2、 不要在析构函数中抛出异常!</p>
<p>构造函数中,成员变量一定要通过初始化列表来初始化的情况:</p>
<p>构造函数中,成员变量一定要通过初始化列表来初始化的有以下几种情况:<br>1、const常量成员,因为常量只能初始化,不能赋值,所以必须放在初始化列表中;<br>2、引用类型,引用必须在定义的时候初始化,并且不能重新赋值,所以也要写在初始化列表中;<br>3、没有默认构造函数的类类型,因为使用初始化列表可以不必调用默认构造函数来初始化,而是直接调用拷贝构造函数;</p>
<p><strong>Q. 临时对象在什么时候会产生?</strong></p>
<p>(1) 用构造函数来做隐式转换函数时,会创建临时对象;<br>(2) 建立一个没有命名的非堆(non-heap)对象,也就是无名对象时,会产生临时对象;<br>(3) 函数返回一个对象值时,会产生临时对象,函数中的返回值会以值拷贝的形式拷贝到被调函数栈中的一个临时对象。</p>
<p><strong>Q. 成员函数里memset(this,0,sizeof(*this))会发生什么?</strong></p>
<p>有时候类里面定义了很多int,char,struct等C语言里的那些类型的变量,我习惯在构造函数中将它们初始化为0,但是一句句的写太麻烦,所以直接就memset(this, 0, sizeof *this);将整个对象的内存全部置为0。对于这种情形可以很好的工作,但是下面几种情形是不可以这么使用的:<br>1.类含有虚函数表:这么做会破坏虚函数表,后续对虚函数的调用都将出现异常<br>2.类中含有C++类型的对象:例如,类中定义了一个list的对象,由于在构造函数体的代码执行之前就对list对象完成了初始化,假设list在它的构造函数里分配了内存,那么我们这么一做就破坏了list对象的内存。</p>
<p>拷贝构造函数在哪几种情况下会被调用?</p>
<p>在C++中,下面三种情况会调用拷贝构造函数(有时也称“复制构造函数”):<br>1) 一个对象作为函数参数,以值传递的方式传入函数体;<br>2) 一个对象作为函数返回值,以值传递的方式从函数返回;<br>3) 一个对象用于给另外一个对象进行初始化(常称为复制初始化);</p>
<p><strong>Q. 什么时候必须重写拷贝构造函数?</strong></p>
<p>一个比较简单的原则:如果你需要定义一个非空的析构函数,那么,通常情况下你也需要定义一个拷贝构造函数。<br>更通常的原则是:<br>1)对于凡是包含动态分配成员或包含指针成员的类都应该提供拷贝构造函数;</p>
<p>注意,在提供拷贝构造函数的同时,还应该考虑重载”=”赋值操作符号。</p>
<p><strong>Q. 何时编译器会自动生成默认构造函数?</strong></p>
<p>其实默认构造函数也是分为两类的:有用的、无用的。所谓有用的标准也是就默认构造函数会为我们的类做一些初始化操作。那么无用的就不会做任何工作,从而对我们的类也就没有任何意义。所以,我们通常所说的默认构造函数是指有用的默认构造函数,其英文名字叫 nontrivial default constructor。<br>但是,对于以下四种情况,编译器会自动生成默认构造函数:<br>1)如果一个类没有任何构造函数,但是含有一个类类型的成员变量,该成员对象有nontrivial default constructor,此时编译器会为该类合成一个默认的构造函数;<br>2)如果一个类没有任何构造函数,但是该类继承自含有默认构造函数的基类,该基类有nontrivial default constructor,此时编译器会为该类合成一个默认的构造函数;<br>编译器这样的理由是:因为派生类被合成时需要显式调用基类的默认构造函数。<br>3)如果一个类没有任何构造函数,但是该类声明或继承了虚函数,含有任何virtual function table(或vtbl)、pointer member(或vptr),此时编译器会为该类合成一个默认的构造函数;<br>编译器这样做的理由很简单:因为这些vtbl或vptr需要编译器隐式(implicit)的合成出来,那么编译器就把合成动作放到了默认构造函数里面。所以编译器必须自己产生一个默认构造函数来完成这些操作。<br>4)如果一个类没有任何构造函数,但是该类含有虚基类,此时编译器会为该类合成一个默认的构造函数;<br>除了以上四种情况,编译器并不会为我们的类产生默认构造函数。</p>
<p><strong>Q. 何时编译器会自动生成拷贝构造函数?</strong></p>
<p>只有在4种情况下编译器才会给我们生成缺省拷贝构造函数:<br>1)类包含的成员变量是object,并且这个object的类有拷贝构造函数。<br>2)类继承自一个基类,这个基类有拷贝构造函数。<br>3)类声明了1个或者多个虚函数。<br>4)类继承自一个基类,这个基类有1个或者多个虚函数。</p>
<p><strong>Q. 面向对象的理解?</strong></p>
<p>面向对象是一种程序设计方法。面向对象有三大特性:封装,继承,多态。</p>
<p>1) 封装:</p>
<blockquote>
<p>封装可以隐藏实现细节,使得代码模块化;封装是把过程和数据包围起来,
漫谈C++内存分配与管理
http://whatbeg.com/2019/04/16/cppmemory.html
2019-04-16T12:32:13.000Z
2019-06-05T03:41:41.326Z
<p>本文谈谈C++的内存分配与管理,主要包括内存布局,分配,管理,解配,以及内存错误和防范措施。<br>漫谈漫谈,想到什么谈什么,不要在意前后衔接。</p>
<p>首先要了解程序占用的内存布局,顺便可以了解下对应的操作系统上的真实存储布局。</p>
<p>通常一个由 C/C++ 编译的程序占用的内存分为以下 5 个部分:</p>
<p>1) 栈区(stack): 由编译器自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。<br>2) 堆区(heap): 一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表。<br>3) 全局区(静态区)(static): 全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。程序结束后由系统释放。<br>4) 文字常量区: 常量字符串等只读数据放在这里的。程序结束后由系统释放。<br>5) 程序代码区: 存放函数体的二进制代码。</p>
<p>同时,一个linux进程的虚拟存储器结构如下:(从高地址到低地址)</p>
<p>1) 内核虚拟存储器,包括与进程相关的数据结构(页表,task_struct, mm_struct, 内核栈等),物理存储器,内核代码和数据<br>2) 用户栈 —对应程序栈区<br>3) 共享库的存储器映射区域 —从系统中加载共享库的存储区<br>4) 运行时堆 —对应程序堆区<br>5) 未初始化数据和已初始化数据 —从可执行文件中加载,对应静态区<br>6) 程序文件(代码) —从可执行文件中加载,对应程序代码区</p>
<p>其中这些区域中,最重要的,最常用到的就是堆区和栈区了,他们的区别如下:</p>
<p>C++中堆和栈的区别:</p>
<p>管理方面,需要自己分配/清除<br>空间大小方面,堆最大可达4G(32位),而栈大小有限制,一般8M<br>碎片方面:堆分配和回收一段时间后可能产生碎片,栈一定不会<br>生长方向:栈往低地址生长,堆往高地址生长<br>分配方式:栈可动态分配也可静态分配,堆只能动态分配<br>分配效率:栈是机器系统提供的数据结构,而堆是语言层提供的数据结构,效率不一样</p>
<p>栈其实要比堆快,原因在于:</p>
<p>1) 栈是本着LIFO原则的存储机制, 对栈数据的定位相对比较快速, 而堆则是随机分配的空间, 处理的数据比较多, 无论如何, 至少要两次定位.<br>2) 栈是由CPU提供指令支持的, 在指令的处理速度上, 对栈数据进行处理的速度自然要优于由操作系统支持的堆数据.<br>3) 栈是在一级缓存中做缓存的, 而堆则是在二级缓存中, 两者在硬件性能上差异巨大.<br>4) 各语言对栈的优化支持要优于对堆的支持, 比如swift语言中, 三个字及以内的struct结构, 可以在栈中内联, 从而达到更快的处理速度.</p>
<p>内存的布局有了,那么怎么分配内存来存储程序中的数据结构呢?这就涉及到了 malloc/free 库函数和 new/delete 操作符。</p>
<p>malloc/free 和 new/delete 区别如下:</p>
<p>a) malloc/free是库函数,new/delete是操作符<br>b) malloc/free只分配内存,new/delete还负责构造/析构对象<br>c) malloc/free分配失败会返回NULL,new/delete分配失败会返回bad_alloc<br>d) new/delete分配可以自动计算需要的字节数,malloc/free需要人为指定</p>
<p>另外一个比较重要的区域是静态区,C++的对象有三种:栈区对象,堆区对象和静态区对象,他们有什么异同呢?</p>
<p>三种内存对象的比较:</p>
<p>栈对象:<br>1.栈对象的优势是在适当的时候自动生成,又在适当的时候自动销毁,不需要程序员操心;<br>2.栈对象的创建速度一般较堆对象快。因为分配堆对象时,会调用operator new操作,operator new会采用某种内存空间搜索算法,而该搜索过程可能是很费时间的,产生栈对象则没有这么麻烦,它仅仅需要移动栈顶指针就可以了。<br>3.通常栈空间容量比较小,一般是1MB~2MB,所以体积比较大的对象不适合在栈中分配。<br>4.特别要注意递归函数中最好不要使用栈对象,因为随着递归调用深度的增加,所需的栈空间也会线性增加,当所需栈空间不够时,便会导致栈溢出,这样就会产生运行时错误。</p>
<p>堆对象:<br>1.其产生时刻和销毁时刻都要程序员精确定义<br>2.相比于栈空间,堆的容量要大得多</p>
<p>静态对象:<br>1.全局对象:全局对象为类间通信和函数间通信提供了一种最简单的方式<br>2.类的static成员:属于类,为所有类对象所共享<br>3.局部静态对象:主要可用于保存该对象所在函数被屡次调用期间的中间状态</p>
<p>内存分配的时候,C/C++ 会注重内存对齐,比如说要求 struct, union, enum 等数据结构都要内存对齐。</p>
<p>那么为什么要内存对齐呢?</p>
<p>《Windows核心编程》里这样说:当CPU访问正确对齐的数据时,它的运行效率最高,当数据大小的数据模数的内存地址是0时,数据是对齐的。例如:WORD值应该是总是从被2除尽的地址开始,而DWORD值应该总是从被4除尽的地址开始,数据对齐不是内存结构的一部分,而是CPU结构的一部分。当CPU试图读取的数值没有正确的对齐时,CPU可以执行两种操作之一:产生一个异常条件;执行多次对齐的内存访问,以便读取完整的未对齐数据,若多次执行内存访问,应用程序的运行速度就会慢。</p>
<p>内存对齐的原则:</p>
<p>结构体的总大小,也就是sizeof的结果,必须是其内部最大成员的”最宽基本类型成员”的整数倍。不足的要补齐。(基本类型不包括struct/class/uinon)。<br>对于union:sizeof(union),以结构里面size最大元素为union的size,因为在某一时刻,union只有一个成员真正存储于该地址。</p>
<p>C++ 内存分配和解配中,很容易发生错误,常见的内存错误有:</p>
<p>1、返回局部变量地址将引起内存错误</p>
<p>2、临时空间过大:操作系统在加载某个应用程序时,都将为其分配一定大小的栈空间,若申请过大的局部变量,可能会引起栈溢出问题。(PC机上Windows和Linux系统一般不必担心这个,因为有虚拟内存机制,但嵌入式开发就要特别注意这个了——资源有限,一旦栈溢出造成程序错误又是很难查的~~~)</p>
<p>3、src 和 dst 内存覆盖:在进行字节内存复制时,常会出现这一问题。因为部分系统库函数并没有提供内存覆盖的检测功能,从而导致错误。</p>
<p>4、动态内存管理错误<br>动态申请的堆内存空间需要自己管理,这可能会导致:<br>a) 申请和释放不一致:由于C++兼容C,而C与C++的内存申请和释放方式不一样,因此在C++程序中,就有两套动态内存管理函数。正常应该是采用C方式申请就用C方式释放,用C++申请的就要用C++方式释放<br>b) 申请和释放量不匹配:申请了多少内存,在使用完成后就要释放多少。若果没有释放或者少释放了就是内存泄露,多释放了也会产生问题。(虽然程序退出后系统会回收堆空间,但严格的应该申请的堆空间要在程序中释放掉。</p>
<p>5、内存泄漏<br>广义的说,内存泄漏不仅仅包含堆内存的泄漏,还包含系统资源的泄漏(resource leak),比如核心态HANDLE,GDI Object,SOCKET, Interface等,这些由操作系统分配的对象也消耗内存,如果这些对象发生泄漏最终也会导致内存的泄漏。而且,某些对象消耗的是核心态内存,这些对象严重泄漏时会导致整个操作系统不稳定。所以相比之下,系统资源的泄漏比堆内存的泄漏更为严重。</p>
<p>其中,最常见的应该属内存泄漏了吧,那么内存泄漏如何检测呢?</p>
<p>检测内存泄漏的关键是要能截获住对分配内存和释放内存的函数的调用。截获住这两个函数,就能跟踪每一块内存的生命周期。比如,每当成功的分配一块内存后,就把它的指针加入一个全局的list中;每当释放一块内存,再把它的指针从list中删除。这样,当程序结束的时候,list中剩余的指针就是指向那些没有被释放的内存。</p>
<p>常见内存泄漏的检测工具有:</p>
<p>Linux下的检测工具:Mtrace、Memwatch、LeakTracer、Valgrind、tcmalloc<br>Windows下的检测工具:windbg工具、MS C-Runtime Library、BoundsChecker、Performance Monitor</p>
<p>为了最大可能的降低内存泄漏的几率,C++提出了智能指针的解决方案。</p>
<p>如何理解智能指针?</p>
<p>裸指针容易造成内存泄漏(忘记释放)、二次释放、程序异常时的处理(参见问题20构造函数能够抛出异常)等问题,使用智能指针能更好的管理堆内存,智能指针也是RAII的一种应用,用于动态管理资源。利用对象离开作用域自动析构的特性,将释放内存的操作托管给一个对象。</p>
<p>有几种常用的智能指针,shared_ptr, unique_ptr, weak_ptr。</p>
<p>顾名思义,shared_ptr指向的对象是可以被共享的,只有再也不被指向的对象会被销毁,而unique_ptr指针不能被共享,只能改变指向,一旦改变,原对象会被销毁。</p>
<p>而weak_ptr具有如下特征:<br>a) weak_ptr不能独立存在,只能从一个shared_ptr产生,其指向shared_ptr指向的内存,但并不拥有该内存,不改变引用计数。<br>b) weak_ptr通过lock()成员可以返回指向该内存的shared_ptr对象,如果已经无效则返回空<br>c) 使用weak_ptr是为了解决循环引用的问题,如果两个对象中都分别包含对对方的引用,则会产生循环引用,计数无法降为0,使用weak_ptr可以解决这个问题,因为其不会增加引用计数。</p>
<p>智能指针内部会维护一个引用计数,即使用指向资源的智能指针个数,如果降到0,即不再有指针指向该对象,那么自动销毁该对象。</p>
<p>引用计数基本思想:对被管理的资源进行引用计数,当一个shared_ptr对象要共享这个资源的时候,该资源的引用计数加1,当这个对象生命期结束的时候,再把该引用计数减少1。这样当最后一个引用它的对象被释放的时候,资源的引用计数减少到0,此时释放该资源。</p>
<p>引用计数改变的情况:<br>作为函数参数:传值则引用计数加1,传引用则引用计数不变 3 作为函数返回值:如果返回值作为右值进行拷贝,则引用计数加1,否则不变</p>
<p>还有一个有趣的问题:C++ 为什么不支持垃圾回收?</p>
<p>C和C++中的垃圾收集都是困难的主题,原因如下:</p>
<p>-2) C and C++ are languages that are created to support all possible use cases. 通用性</p>
<p>-1)C++ 的哲学是”close to the metal”,不愿意加上一些性价比不高的features</p>
<p>0)GC Stop-the-World 带来的性能延迟,有时是不可忍受的,尤其在实时应用中</p>
<p>1)指针可以被转换为整数,反之亦然。这意味着垃圾收集器必须能够准确识别指针和非指针,并且垃圾收集器必须小心,当内存块仍然可达时,不要认为该块是无法到达的。</p>
<p>2)指针不是不透明的。 许多垃圾收集器,如停止和复制收集器,喜欢移动内存块或压缩它们以节省空间。 由于程序员可以明确地查看C和C++中的指针值,因此难以正确实现。 你必须保证,如果某人正在使用类似于整数的方式进行一些棘手的操作,那么如果移动了一块内存,整数也要被正确更新。</p>
<p>3)内存管理可以明确完成。任何垃圾收集器都需要考虑到用户可以随时显式释放内存块。</p>
<p>4)在C++中,分配/取消分配和对象构造/销毁是分开的。一块内存可以分配足够的空间来容纳一个对象,而不需要当场实际构造对象。 垃圾回收器在回收内存时需要知道是否调用可能在那里分配的任何对象的析构函数。 对于标准库容器尤其如此,因为出于效率原因,通常使用std::allocator来使用这个技巧。</p>
<p>5)内存可以从不同的地方分配。 C和C++可以通过内置的freestore(malloc / free或new / delete),也可以通过mmap或其他系统调用从OS获得内存,对于C++,可以从<code>get_temporary_buffer</code>或<code>return_temporary_buffer</code>获得内存。 程序也可能从一些第三方库中获取内存。垃圾收集器需要能够跟踪对这些其他池中的内存的引用,并且(可能)必须负责清理它们。</p>
<p>6)指针可以指向对象或数组的中间。 在像Java这样的垃圾收集语言中,对象引用总是指向对象的开始。 在C和C++中,指针可以指向数组的中间,而在C++中指向对象的中间(如果使用多重继承的话)。 这可能会使检测仍然可达的逻辑复杂化。</p>
<p>所以,简而言之,为C或C++构建一个垃圾收集器是非常困难的。大多数使用C和C++进行垃圾回收的库在方法上都非常保守,在技术上是不健全的 - 例如,他们认为你不会拿一个指针,把它转换为一个整数,写入磁盘,然后加载它稍后再回来。 他们还假定内存中任何一个指针大小的值都可能是一个指针,所以有时候会拒绝释放无法访问的内存,因为有一个指向它的非零的机会。</p>
<p>正如其他人所指出的那样, Boehm GC确实为C和C++做垃圾回收,但受上述限制。</p>
<p>有趣的是,C++ 11包含了一些新的库函数,允许程序员在未来的垃圾收集工作中将内存区域标记为可达和不可达。 将来有可能用这种信息构建一个非常好的C++ 11垃圾收集器。 与此同时,你需要非常小心,不要违反上述规定。</p>
<p>本文谈谈C++的内存分配与管理,主要包括内存布局,分配,管理,解配,以及内存错误和防范措施。<br>漫谈漫谈,想到什么谈什么,不要在意前后衔接。</p>
<p>首先要了解程序占用的内存布局,顺便可以了解下对应的操作系统上的真实存储布局。</p>
<p>通常一个由 C/C++
《软技能》读书笔记
http://whatbeg.com/2019/01/04/softskills.html
2019-01-04T03:54:18.000Z
2019-01-04T03:55:38.854Z
<p>偶然的机会,得阅《软技能》一书。</p>
<p>这本书蛮神奇,对于程序员这个职业代码之外的许多个方面结合作者的经验做了讲述。</p>
<p>涵盖主题从工作效率,工作方法,到博客,演讲,学习方法到健身,理财,营养学等多个方面,非常广泛,给人启发,愚以为软件从业人员值得一读。</p>
<p>其中有几点对我个人比较有启发,在这里分享一下。</p>
<h3 id="跨越墙的能力"><a href="#跨越墙的能力" class="headerlink" title="跨越墙的能力"></a>跨越墙的能力</h3><p>有时候我们学东西,或者做事,会遇到一些瓶颈,会遇到失去兴趣的时候。</p>
<p>失去兴趣是人的本性所致,刚接触一样新东西时,人的大脑总是会特别兴奋,待到持续的做了一段时间后,会有一个倦怠,失去兴趣的时候。这也解释了,为什么很多人都是三分钟热度,某件事听着好玩,开始前常常信誓旦旦说要在这个领域达到一个什么样的效果,然后尝试了一段时间后,就抛诸脑后了(我也常如此哈哈。</p>
<p>而遇到瓶颈也是常有的事,因为人学习能力比较强,初学基本知识很容易学会了,但是要进一步往上升,将自己的技能,思维提高一个层次,却常常不知如何下手,导致卡在一个瓶颈,仍只能处理所在层次的简单问题,无法解决更复杂的问题。</p>
<p>这个时候,作者的建议很简单粗暴,就是硬着头皮继续做下去。学不下去,强迫自己养成习惯去做。有瓶颈,寻找更难的问题,逃离舒适区,强行去做。这必然有一段痛苦的时间,等到熬过去,穿越了这堵“墙”,你会发现,很容易有正循环建立起来,你的水平会提高,然后也会感觉对这件事更有兴趣了,也会感到瓶颈其实也没那么可怕。</p>
<p>当然这也不是绝对的,人各有其天赋,有的人终其一生也跨不过一些墙,所以有时也需要对自己有更清楚的认识。</p>
<p>一切都在穿墙之后。</p>
<h3 id="潜意识的重要作用"><a href="#潜意识的重要作用" class="headerlink" title="潜意识的重要作用"></a>潜意识的重要作用</h3><p>给自己积极的暗示,想想自己所追求的事情达到以后的美好,建立积极的潜意识,常常会使得事情容易一些。</p>
<p>听过一句话,如果你想让一个人做一件事,最好的办法就是让他自己想做。</p>
<p>放到自己身上,如果想让自己去做好一件事,最好的办法是否就是建立潜意识让自己的潜意识自己想做?</p>
<h3 id="像经营企业一样经营自己"><a href="#像经营企业一样经营自己" class="headerlink" title="像经营企业一样经营自己"></a>像经营企业一样经营自己</h3><p>作者提到,作为一个程序员,有时候需要悉心经营自己。</p>
<p>我想,其实不同行业都一样,其实都可以更好地经营自己。</p>
<p>作者提到,作为IT从业者,可以通过博客,演讲,授课等等各种方式来提高自己的知名度,建立自身品牌。</p>
<p>同时可以应用企业经营的许多方法来经营自己,比如最根本的,企业需要为客户提供价值。</p>
<p>其实想要赢得声誉或者是财富,都需要为他人能够带来价值。</p>
<h3 id="目标的重要性"><a href="#目标的重要性" class="headerlink" title="目标的重要性"></a>目标的重要性</h3><p>应该以目标为导向而不是以工作量为导向。</p>
<p>衡量自己进步的标识,应该也是以达到多少个目标,目标给自己或别人带来的影响大小等来衡量。而非自己干了多少活,或者看了多少书…来衡量的。</p>
<h3 id="战略,理念的重要性"><a href="#战略,理念的重要性" class="headerlink" title="战略,理念的重要性"></a>战略,理念的重要性</h3><p>这点同样对各行各业的人都有普世的意义。</p>
<p>不仅要脚踏实地,也要抬头看天。</p>
<p>如果走错了方向,再多的脚踏实地,勤劳奋斗,可能都是南辕北辙。</p>
<p>作为一个人,跟作为企业其实差不多,都需要一个战略,或者说理念。</p>
<p>这个战略和理念又涉及到人生观和价值观,指的是你给自己的发展定下什么样的长远战略规划?你的一生要完成什么事业?你坚持的铁律是什么?哪些是你想要的?哪些是你有的?哪些是你需要放弃的?</p>
<p>想清楚了这些,内心才有一根定海神针,获得坚定而自信。</p>
<p>别小瞧了这两点,自己不坚定和自信,别人怎么坚定地相信你呢?</p>
<p>偶然的机会,得阅《软技能》一书。</p>
<p>这本书蛮神奇,对于程序员这个职业代码之外的许多个方面结合作者的经验做了讲述。</p>
<p>涵盖主题从工作效率,工作方法,到博客,演讲,学习方法到健身,理财,营养学等多个方面,非常广泛,给人启发,愚以为软件从业人员值得一读。<
算法拾珠(二)
http://whatbeg.com/2019/01/04/algorithmpearls.html
2019-01-04T03:54:06.000Z
2019-01-04T04:16:25.124Z
<p>不知怎么的把算法拾珠(一)发到公众号上去了,底稿也删了,所以算法拾珠(一)只能见公众号文章了。</p>
<p>本文记录了三种算法问题的基本问题和一系列扩展问题(followup),可供深入理解这几类问题的解法。</p>
<p>这三类问题包括:</p>
<blockquote>
<p>X sum问题:一个序列中取X个数字,使它们的和凑成定值。<br>股票买卖问题:一段连续天数,股票有价格可以买卖,问最大获利。<br>链表环问题:链表有环的条件,检测和分析。</p>
</blockquote>
<h2 id="X-sum-问题"><a href="#X-sum-问题" class="headerlink" title="X sum 问题"></a>X sum 问题</h2><p>1) Two Sum, LeetCode #1</p>
<p>我当初的解法:排序,二分法<br>最优解法:一边遍历,一边插入、查询hashmap,O(n)复杂度,O(n)空间</p>
<p>2) 3Sum, LeetCode #15</p>
<p>我的解法:排序,从左到右枚举,然后用双指针往中间移动<br>最优解法:目前看到的最优解法如上,O(n^2logn)时间,O(1)空间</p>
<p>3) 3Sum Cloest, LeetCode #16</p>
<p>解法:类似3Sum那样去找,只是每次记录一下与target的差距,最后取一个最小值。O(n2logn)时间,O(1)空间</p>
<p>4) 4Sum, LeetCode #18</p>
<p>解法: 还是双指针的思想,只不过比3Sum多一层而已。O(n^3)时间,O(1)空间</p>
<p>*5) 4Sum II, LeetCode #454</p>
<p>解法:首先A,B,C,D都排序,枚举A,B的元素,然后还是双指针的思想,一个指针在C从左往右,一个指针在D从右往左。<br>这题比较偏了,不属于常规kSum,不做重点考虑。</p>
<blockquote>
<p>由此可得,k-Sum问题当k==2时可以用hashmap的额外空间以O(n)的复杂度解决。而当k>2时可以用递归以及双指针法来解决。</p>
</blockquote>
<figure class="highlight nimrod"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line">// kSum通用代码</span><br><span class="line"><span class="built_in">void</span> kSum(<span class="keyword">const</span> vector<<span class="built_in">int</span>>& nums, <span class="built_in">int</span> k, <span class="built_in">int</span> target, <span class="built_in">int</span> <span class="keyword">from</span>, <span class="built_in">int</span> <span class="keyword">end</span>, vector<<span class="built_in">int</span>>& cur, vector<vector<<span class="built_in">int</span>> >& <span class="literal">result</span>) {</span><br><span class="line"> <span class="keyword">if</span> (k < <span class="number">2</span>) <span class="keyword">return</span>;</span><br><span class="line"> <span class="keyword">if</span> (k == <span class="number">2</span>) {</span><br><span class="line"> <span class="built_in">int</span> l = <span class="keyword">from</span>, r = <span class="keyword">end</span>;</span><br><span class="line"> <span class="keyword">while</span> (l < r) {</span><br><span class="line"> <span class="keyword">if</span> (nums[l] + nums[r] == target) {</span><br><span class="line"> vector<<span class="built_in">int</span>> tmp(cur);</span><br><span class="line"> tmp.push_back(nums[l]);</span><br><span class="line"> tmp.push_back(nums[r]);</span><br><span class="line"> <span class="literal">result</span>.push_back(tmp);</span><br><span class="line"> <span class="keyword">while</span> (l < r && nums[l] == nums[l+<span class="number">1</span>]) l++;</span><br><span class="line"> <span class="keyword">while</span> (l < r && nums[r] == nums[r-<span class="number">1</span>]) r--;</span><br><span class="line"> l++, r--;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span> <span class="keyword">if</span> (nums[l] + nums[r] > target)</span><br><span class="line"> r--;</span><br><span class="line"> <span class="keyword">else</span></span><br><span class="line"> l++;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">for</span> (<span class="built_in">int</span> i=<span class="keyword">from</span>;i<=<span class="keyword">end</span>-k+<span class="number">1</span>;i++) {</span><br><span class="line"> <span class="keyword">if</span> (i > <span class="keyword">from</span> && nums[i] == nums[i-<span class="number">1</span>]) <span class="keyword">continue</span>;</span><br><span class="line"> cur.push_back(nums[i]);</span><br><span class="line"> kSum(nums, k-<span class="number">1</span>, target-nums[i], i+<span class="number">1</span>, <span class="keyword">end</span>, cur, <span class="literal">result</span>);</span><br><span class="line"> cur.pop_back();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br></pre></td></tr></table></figure>
<h2 id="股票买卖问题"><a href="#股票买卖问题" class="headerlink" title="股票买卖问题"></a>股票买卖问题</h2><p>给定n天的股票价格数据,n天内,买卖股票一次或多次,获得最大收益。</p>
<p>1) Best Time to Buy and Sell Stock, LeetCode #121</p>
<p>要求买卖一次股票,获得最大收益。<br>解法:贪心,即可,可以把股价看做折线图,只能买卖一次,所以我们只需找最低的波谷和最高的波峰,相减即得。当然要保证波峰在波谷之后。<br>具体的,从左往右扫描,维护当前最小值,每次用当天的股价减去目前最小股价(保证波峰在波谷之后),求得一个max值,即为答案。<br>复杂度O(n), O(1),已经最优了。</p>
<p>2) Best Time to Buy and Sell Stock II, LeetCode #122</p>
<p>可以买卖任意次,获得最大收益。<br>解法:还是把股价波动看成折线图,既然可以无线买,那么我们肯定是贪心地,每个上升斜坡的钱都要挣到。<br>所以,具体的,只要price[i] > price[i+1],一定可以挣这份钱。最大收益就是这样的钱的累加。<br>复杂度O(n), O(1),也是最优。</p>
<p>3) Best Time to Buy and Sell Stock III, LeetCode #123</p>
<p>至多买卖两次。<br>我的解法:类似前面的思路,无非是要找最多两个不重叠的波谷-波峰对,我们枚举分界点,求出分界点左边的最优波谷-波峰和右边的最优波谷-波峰,相加为答案。<br>具体的,用O(n)的空间存储前缀的最优波谷-波峰,同样用O(n)的空间存储后缀的最优波谷-波峰,最后枚举分界点,两个利润一相加为答案。<br>此解法复杂度为O(n), O(n),需要一定空间来存储。</p>
<p>更优解法:<br>动态规划的思想。<br>每天只可能是种状态之一:持有第一支,卖出第一支,持有第二支,卖出第二支。<br>dp[i][state]表示到第i天,状态为state所获的最大收益。<br>则<br><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">dp[<span class="string">i</span>][<span class="symbol">sell2</span>] = max(dp[<span class="string">i-1</span>][<span class="symbol">sell2</span>], dp[<span class="string">i-1</span>][<span class="symbol">hold2</span>]+i)</span><br><span class="line">dp[<span class="string">i</span>][<span class="symbol">hold2</span>] = max(dp[<span class="string">i-1</span>][<span class="symbol">hold2</span>], dp[<span class="string">i-1</span>][<span class="symbol">sell1</span>]-i)</span><br><span class="line">dp[<span class="string">i</span>][<span class="symbol">sell1</span>] = max(dp[<span class="string">i-1</span>][<span class="symbol">sell1</span>], dp[<span class="string">i-1</span>][<span class="symbol">hold1</span>]+i)</span><br><span class="line">dp[<span class="string">i</span>][<span class="symbol">hold1</span>] = max(dp[<span class="string">i-1</span>][<span class="symbol">hold1</span>], 0-i)</span><br><span class="line"></span><br><span class="line">return dp[<span class="string">n</span>][<span class="symbol">sell2</span>]</span><br></pre></td></tr></table></figure></p>
<p>由于dp[i][states]只与dp[i-1][states]有关,所以可以直接用一个变量表示每种状态,所以可以做到O(n), O(1)。</p>
<p>4) Best Time to Buy and Sell Stock IV, LeetCode #188</p>
<p>至多买卖k次。<br>类似上题的思想,动态规划法,只是4种状态变成2k种状态而已。<br>dp[i][k][h]表示到第i天,目前操作股票(持有/卖出)是第k支,操作状态为h(h=0:持有,h=1:卖出)。<br>则有如下转移方程:<br><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">dp[<span class="string">i</span>][<span class="symbol">k</span>][<span class="string">1</span>] = max(dp[<span class="string">i-1</span>][<span class="symbol">k</span>][<span class="string">1</span>], dp[<span class="string">i-1</span>][<span class="symbol">k</span>][<span class="string">0</span>]+prices[i]);</span><br><span class="line">dp[<span class="string">i</span>][<span class="symbol">k</span>][<span class="string">0</span>] = max(dp[<span class="string">i-1</span>][<span class="symbol">k</span>][<span class="string">0</span>], dp[<span class="string">i-1</span>][<span class="symbol">k-1</span>][<span class="string">1</span>]-prices[i]);</span><br></pre></td></tr></table></figure></p>
<p>同样,由于第一维(i)只与i-1有关,所以该存储也可省去。<br>复杂度O(kn),空间O(k)。<br>这题要注意的是,当k>=n/2时,实际上等价于没有次数限制的情况。对应题(2)。</p>
<p>3) Best Time to Buy and Sell Stock with Cooldown, LeetCode #309</p>
<p>可以无限次买卖,但是每次卖出后都需要隔一天才能再次买入。</p>
<p>解法:<br>动态规划求解,由于不限买卖次数,只是需要隔一天。<br>我们令每天只有两种状态:持有(hold),套现(cash),其中持有包括不买卖和买入。<br>cash[i]表示第i天,最大现金流,有两个子问题:一是之前就已经套现,而是之前持有,今天套现。去这两者的最大值为今天最大现金流。持有类似。<br>那么有如下方程:<br><figure class="highlight gcode"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 套现取 |之前套现| 与 |之前持有,今天套现| 二者的最大值,后者要减去一定的手续费。</span></span><br><span class="line">cash[i] = max<span class="comment">(cash[i-1], hold[i-1]+prices[i]-fee)</span></span><br><span class="line"><span class="comment">// 持有取 |之前持有| 与 |前天及之前套现,今天买入| 二者的最大值。</span></span><br><span class="line">hold[i] = max<span class="comment">(hold[i-1], cash[i-2]-prices[i])</span></span><br></pre></td></tr></table></figure></p>
<p>4) Best Time to Buy and Sell Stock with Transaction Fee, LeetCode #714</p>
<p>可以无限次买卖,但是每次卖出操作都有手续费fee,求最大收益。</p>
<p>解法:<br>动态规划求解,由于不像限制买卖次数的题,我们不能去照样维护一个dp[k][2],因为不知道k是多少,我们也无需考虑这次买卖是第几支,因为不限制随便买。<br>当然,也可以令k为n/2,但此时我们用另一种方法求解。<br>我们令每天只有两种状态:持有(hold),套现(cash),其中持有包括不买卖和买入。<br>那么有如下方程:<br><figure class="highlight gcode"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 套现取 |之前套现| 与 |之前持有,今天套现| 二者的最大值,后者要减去一定的手续费。</span></span><br><span class="line">cash[i] = max<span class="comment">(cash[i-1], hold[i-1]+prices[i]-fee)</span></span><br><span class="line"><span class="comment">// 持有取 |之前持有| 与 |之前套现,今天买入| 二者的最大值。</span></span><br><span class="line">hold[i] = max<span class="comment">(hold[i-1], cash[i-1]-prices[i])</span></span><br></pre></td></tr></table></figure></p>
<blockquote>
<p>总而言之,解决股票类问题,如果简单的话,可以直接贪心思想来做,当然该贪心方法也是动态规划的特例。 如果稍微复杂一点,那么可以用动态规划来做。 只要把握好每天的状态,就好定制状态转移方程了。</p>
</blockquote>
<h2 id="链表环问题"><a href="#链表环问题" class="headerlink" title="链表环问题"></a>链表环问题</h2><p>链表环问题是比较常问的一类问题。我们系统梳理一下。</p>
<p>1) 判断单链表是否有环</p>
<p>解法:解法已经烂大街了,设置两个指针p1, p2,一个每次走一步,一个每次走两步,最后如果 p1 == p2,说明有环,如果其中一个为NULL,则没有环。</p>
<p>2) 证明这样做的正确性</p>
<ul>
<li>如果没有环,走得快的那个一定会率先走到NULL,结束。</li>
<li>如果有环,那么需证明,p1, p2 一定会相遇,从而得出有环的结论。<br>考虑 p1, p2 的距离,因为如果有环,最后 p1, p2 都会进入环,如果进入环的时候他们的距离为d,由于 p2 比 p1 快一步,所以他们的距离每次都会缩短1,所以他们的距离会呈现 d,d-1,d-2,…1,0 这样的趋势,最终距离为 0,相等判环。<br>且由于d < r(环长),所以,相遇的时候,p1绝对还没有绕环一圈。</li>
</ul>
<p>3) 求有环单链表的环长</p>
<p>解法:我们走到相遇点以后,从相遇点出发,一直走,总会走回到相遇点,这时走的步数就是环长。<br>另一种比较绕的思路:从相遇点开始 p1 和 p2 继续按照原来的方式向前走,直到二者再次相遇,此时经过的步数就是环上节点的个数。<br>因为 p1 == p2,可以看成他们初始距离为r(环长),此后每步初始距离减1,减到0,走的步数就是环长。</p>
<p>4) 如果存在环,找出环的入口点</p>
<p>假设链表总长为 L,head 到入口节点距离为 a,入口节点到相遇点距离为x,则环长 $r = L - a$<br>假设当 p1, p2 第一次相遇时,p1 走了 s 步,则 p2 走了 2s 步。即 $s = a+x$。<br>由于此时相遇,则有 $s + nr = 2s, n \ge 1$。<br>则有 $nr = a+x$,$a = nr - x = (n-1)r + r - x = (n-1)r + L - a - x$。<br>也就是说,我们从起点 head 走到入口节点,和从相遇点走 0 圈或多圈到达入口节点的距离是一样的。<br>所以,一个指针 p3 从 head 出发,一个 p4 从相遇点出发,p3==p4 时,到达入口节点。</p>
<p>5) 求有环单链表的链表长</p>
<p>入口节点知道了,则距离 a 知道了,环长 r 也知道了,所以 a + r = L。</p>
<p>6) 如果存在环,求出环上距离任意一个节点最远的点(对面节点)</p>
<p>其实这题相当于求链表的中间点,仍可以用一快一慢双指针解决。一个走一步,一个走两步。</p>
<p>7) 为什么快慢指针步长为2,为3, 4, 5…行不行?</p>
<p>可以。下面证明在有环链表中,步长$k=3,4,5…$的时候依然存在一个相遇点,检测出环。<br>即要证明存在 s, $X_s == X_{2s}$。<br>设 s 是第一个大于 a 的且是 r 的倍数的数。且 $X_{2s}$ 可被看做 s 多走了 $(k-1)s$ 步到达的地点。<br>即 $X_{2s} = X_s + ((k-1)s \% r)$,又 s 是 r 的倍数,所以后项等于 0。所以有 $X_s == X_{2s}$。<br>所以无论对于多大的 k (k > 1),都存在一个 s,使得两指针相遇,判断出环。</p>
<p>8) 步长改成3会不会变快?不能,为什么?相遇所经过的步数与环和步长有什么关系?</p>
<p>仍然设 head 到相遇点距离为 s,设步长为 k,则有 $s + nr = ks$。<br>即有:$nr = (k-1)s \to s = nr / (k-1)$。<br>这就是相遇所经过的步数与环和步长的关系。<br>那么 $s = nr / (k-1)$,最好的情况是,$s = r$ 即 $n / (k-1) = 1 \to k = n+1$。<br>又因为,$n \ge 1$,因为快指针至少比慢指针多走一圈(不绕一圈无法相遇)。<br>所以 $k >= 2$,由于快指针走得复杂度是 $O(nk)$,所以 k 越小越好,为 2 最佳。慢指针复杂度是 $O(n)$。</p>
<p>9) 会不会在多点相遇?</p>
<p>会,但是考虑这个无意义了,因为只要相遇,环已经检测出来了。</p>
<blockquote>
<p>总的来说,遇到链表环问题,基本上用双指针来做,快的指针步长为2是有道理的,能够使得相遇前快指针走的步数最少,也即时间复杂度最小。当然,任意的k>1都能够作为快指针的步长。</p>
</blockquote>
<h2 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h2><p><a href="https://stackoverflow.com/questions/5130246/why-increase-pointer-by-two-while-finding-loop-in-linked-list-why-not-3-4-5" target="_blank" rel="external">Why increase pointer by two while finding loop in linked list, why not 3,4,5?
</a></p>
<p>不知怎么的把算法拾珠(一)发到公众号上去了,底稿也删了,所以算法拾珠(一)只能见公众号文章了。</p>
<p>本文记录了三种算法问题的基本问题和一系列扩展问题(followup),可供深入理解这几类问题的解法。</p>
<p>这三类问题包括:</p>
<blockquote
2019,再出发
http://whatbeg.com/2019/01/04/annualsummary2018.html
2019-01-04T03:53:54.000Z
2019-01-05T05:15:02.811Z
<p>2019 年元旦是在旅行中度过的,这次行程之一是回到成都的母校。</p>
<p>母校有一些变化,但变化不大,外边加了一座天桥,南门正准备修地铁,以后出行应该会更方便了。最惨的是,作为一名“校外人士”,想买东西,发现支付宝微信一概不能用,只能刷卡。</p>
<p>旅行完毕,是时候该对过去的一年做一个整理和总结了,对来年也需要定一个目标和规划。就像一个船长,需要定期检查船体和功能,需要根据海上的情况及时做出反应,调整航向。</p>
<p>这一年,到了确定就业去向的一年,近 20 年读书生涯也即将告罄,究竟会去到哪里开始自己的职业生涯,开始自己的为生存,生活所奋斗呢?这个问题我上半年也一直在纠结。</p>
<p>工作方面,项目,论文,都要抓起来,事务繁多,但是也在尽力的协调,尽量更合理的安排时间,逐渐形成目标导向,去粗取精式的工作方式。</p>
<p>其次最深刻的感觉是,这几年每一年的思维都会有所成长。不过也能理解,20 多岁的年纪,正是思维最为活跃的年纪,也是三观逐渐成型的年纪,自然会有很多新的体悟。这个年纪也是人生中创造力,精力都最旺盛的几年,当然希望自己好好把握,不要浪费。</p>
<h2 id="学习工作篇"><a href="#学习工作篇" class="headerlink" title="学习工作篇"></a>学习工作篇</h2><p>今年学习工作方面主要分为几块时间:<br>3-8 月:ForestLayer 研究,实验和论文写作<br>8-10 月:找工作<br>10-12 月:横向项目,论文修改</p>
<p>回过头来才发现,原来 ForestLayer 论文整了这么长的时间,其实把整个工作的脉络捋下来,感觉根本用不了那么长的时间。<br>这里需要反省的是,自己在一些实验设计上犯了惰于思考的错误,常常犯错误或者实验设计有问题,所以需要重跑实验,同时以为反正机器够用,就一直跑即可。殊不知,如果花更多时间去思考实验不顺利背后的原因,花时间去设计完善的实验,仔细检查各种条件,可能不会有这么多的返工,整个工作也会更高效一些。</p>
<p>这里后来有感而发写就了一篇文章《警惕虚假的忙碌》,可见博客首页公众号。</p>
<p>除了实验设计,论文的写作和修改也花了一大部分时间。开始是由我写一稿,我吭哧吭哧写完以后被师兄喷了个狗血淋头:前后逻辑不通,叙述过于细节没有分清主次,用词不准,描述不清…后面师兄帮忙一句一句的抠,前后抠了近两个多月,基本上算是师兄全部重写了一遍。之所以犯这些错误,其根本原因在于没有把握学术论文的本质,学术论文其实是一个填补知识差的过程,你把自己的发现告诉别人,填补这个知识差,如果你的发现够好够有意义,总有一天别人会认可。<br>这个填补的过程就需要注意方式方法:比如逻辑要通顺,便于理解,用词准确且严谨,短短的篇幅无需涉及大量细节,等等。</p>
<p>这部分也总结了一些学术论文写作中自己犯过的一些错误,可见公众号文章《学术论文写作》。</p>
<p>找工作的时候,一开始的时候整个人还是比较焦虑的,生怕因为面试发挥不佳或者运气不好而不能进入到心仪的公司。</p>
<p>不过最后自己运气也是一如既往的好,虽然自我感觉面试得并非很好,但是居然拿到了心仪的 offer。可能是专业匹配的缘故或是其他原因吧。</p>
<p>除了最心仪的这家,还面了其他几家,因为不知道自己在对方那里评价如何,陆续也拿到了几个 offer,最后综合了一下平台,成长性和未来的发展,还是选择了这家。</p>
<p>总结一下就是,专业匹配加上准备充分的话其实大概率没问题。<br>如果不匹配,但是准备充分的话也没大问题。<br>如果匹配,但是准备不充分,那么可能性会降低。<br>如果既不匹配也准备得不充分,那么大概率凉。</p>
<p>关于找工作的心得一直想找个机会写篇文章,但是还没来得及,这个可以列入 2019 年计划当中。</p>
<p>由于横向项目的关系,也会遇到一些业界的人士过来交流的机会。了解到如今机器学习,深度学习的火热,带动了众多的公司开始采用机器学习方法。虽说深度学习像一把尖刀,但是有很多公司的业务却并不符合深度学习的模式,比如非图像,语音,文本数据。而且这类业务的需求也并不小。既然深度学习不适用,那么只能使用传统机器学习算法,在这个过程中,就会遇到很多特征工程的问题,以及复杂异构数据源的特征提取问题(在去年年终总结的未来篇有提到)。有时候往往特征工程还是比较重要的一环,直接影响模型性能的上限。同时,业界也在寻求 AutoML 的帮助,希望能够自动地提取特征,或者改善模型。</p>
<p>原来模型是想自动化学习规律,如今 AutoML 是想自动化模型的选择,自动化上加了一层自动化。相信在 2019 年 AutoML 应该会逐步放出光彩。不出意外,未来我会写一篇总结 AutoML 的文章,抛砖引玉,发表一些浅见。</p>
<h2 id="读书与写作"><a href="#读书与写作" class="headerlink" title="读书与写作"></a>读书与写作</h2><p>今年的读书量并不算多,17 本,虽说相比 2017 年增加了 7 本,但是其实还是不够。</p>
<p>最近几个月一个明显感悟就是,读书并不一定要一字一句读完才叫读书,那样的读书更像是一种任务型读书,花费了很多时间,收效却并不明显。这里其实涉及到了一个哲学问题,就是需要分清主要矛盾和次要矛盾,不要眉毛胡子一把抓。所以在 2019 年,我决定要把自己的阅读方式优化一下,从原来的接近任务型的读书法优化成目标型的高效读书法。甚至构建一个读书系统(这点后面说),然后不断去优化和迭代这个系统,而不是拿一本,啃完,再下一本这样弱目的性地读书了。这些思考启发自辉哥(辉哥奇谭)。</p>
<p>今年的读书清单中主要包括技术类的书籍和商业金融理财类的书籍,历史和文学读的很少。<br>技术书籍主要包括 C++ 系的书籍,以及 CSAPP,其中 CSAPP 堪称神作,对计算机底层原理进行了鞭辟入里的讲解,适合常读。<br>商业金融理财类中印象比较深的包括《穷查理宝典》,《富爸爸穷爸爸》和《见识》。这几本书的共同之处在于,都鼓励人们去提高自己的思维层次(或者说见识),不管是理财相关的或是普世生活相关的。人与人之间的根本差别往往在于思维层次。后来,有感而发,也写了一篇文章《也说思维层次》放在公众号上。其中《穷查理宝典》包含了尤其多的芒格老爷子的普世智慧,推荐一读。</p>
<p>年末的时候开始重读老子,重新接触老庄哲学,发现另有一片乾坤,回想 2017 年的时候觉得道德经不能够打动我,现在却觉得老庄哲学颇有一番风味。正如南怀瑾老师所说的,中国自汉以来这几千年,外示儒术,内里还是黄老。这方面准备进一步阅读研究一下。</p>
<p>一些书籍目前已经确定在 2019 年的阅读清单中:《老子》,《庄子》,《庄子諵譁》,《如何有效阅读一本书》,《原则》…具体的根据实际情况调整。</p>
<p>2019 年,希望自己能够掌握快速而有效的阅读,能够读更多的书,博观而约取。同时总结出自己的一套读书系统,即驱动阅读的一页纸。(关于系统是什么可见辉哥公号,他经常提)。同时,希望自己保证阅读时间,目前的量化规划是,每周至少 6 个番茄钟用来读书。读书量的规划暂时希望至少比 2018 年提升一倍,达到 34 本以上。</p>
<p>写作上来说,今年写就了 20 篇文章,刚刚好实现了去年的计划。</p>
<p>文章主要年初写得多,此后由于工作繁忙,博客一路沉寂,不过在公众号上更新了几篇文章。直到 12 月,接触了一些新技术,以及对之前的文章做了整理,发表了出来。</p>
<p>总体来看,20 篇文章还算可以,不过效率仍然有很大提高的余地。2019 年计划进一步提高文章写作效率和文章质量,学习高效的写作方法,争取分享更多的技术总结和非技术的思考给大家。最好是能够整理形成一个写作系统。读书系统和写作系统可以统归于“兴趣系统”。</p>
<p>从量上来规划的话,希望能够达到 30 篇文章,同时不断提高质量。</p>
<h2 id="2018-计划检查篇"><a href="#2018-计划检查篇" class="headerlink" title="2018 计划检查篇"></a>2018 计划检查篇</h2><blockquote>
<p>阅读 20 本书</p>
</blockquote>
<p>没有达到,只读了 17 本,甚至直到今天,才知道自己目标还没达到,中途也没有根据实际情况作调整,浪费了很多时间。</p>
<p>鉴于此,在 2019 年,需要执行 OKR 工作法,来跟进所有计划的进度,并根据完成情况进行调整。这样应该会有助于目标的确保实现。</p>
<blockquote>
<p>做出两篇论文</p>
</blockquote>
<p>发表一篇,投递一篇在等结果,希望有好消息。</p>
<blockquote>
<p>拿到心仪 offer</p>
</blockquote>
<p>完成。</p>
<p>综合 2018 计划完成情况来看,不算太令人满意,一些明明可以按时按量完成的,因为没有及时检查进度,有所松懈,导致了没有完成。希望 2019 年建立一个 OKR 表来做监督检查。</p>
<p>同时,任务有些过于少了,其他方面都没有计划,可以适量增加计划。</p>
<h2 id="其他篇"><a href="#其他篇" class="headerlink" title="其他篇"></a>其他篇</h2><p>2018 其实还打算了几个事情,一个就是找完工作去学毛笔书法,可由于面试跨度太大,甚至 12 月份的周末还在面试,所以没有能够在周末去学书法,在 2019 年初,打算把这块提上日程。<br>其次,期间还计划了健身增肌,坚持了 1 个多月的每周 3 次健身,后面由于天气转凉,没了下文。应该在 2019 年好好计划一下这个事情。</p>
<h2 id="收获篇"><a href="#收获篇" class="headerlink" title="收获篇"></a>收获篇</h2><p>总的来说,2018 年,我收获了一些思维方式的转变,思维层次的提高,收获了一些高效学习和工作的方法,虽然情况各异,有的执行了,有的还没执行,有的执行的不太严格。同时,也收获了一份不错的工作和一篇论文。</p>
<p>慢慢走,慢慢看,希望明年也会有丰富的收获。</p>
<h2 id="2019-计划篇"><a href="#2019-计划篇" class="headerlink" title="2019 计划篇"></a>2019 计划篇</h2><ul>
<li>基本战略:目标导向,高效。<br>主要围绕这几个关键词,转变思维,彻底提高工作效率。其实每个人到了这个阶段,这个年纪,高效性是必然需要追求的了。</li>
<li>方法论:系统构建<br>构建几套系统,用系统来提高效率,保证稳定的发挥和收益。就像我们有了很多很多的算法,就会构建一个抽象系统来支撑它一样。</li>
</ul>
<blockquote>
<p>每周至少 6 个阅读番茄钟,阅读 34 本书以上<br>每周至少 2 个写作番茄钟,写作 30 篇文章以上<br>总结自己的两大系统:兴趣系统,工作系统<br>发表 ForestLayer 论文<br>增肌,具体量化写到 OKR 里面</p>
</blockquote>
<p>2019,重新出发,也要从心出发,跟随自己的内心,丰富自己的内心,提升自己的思维,强化自己的技能,并给他人带来价值。</p>
<p>2019 年元旦是在旅行中度过的,这次行程之一是回到成都的母校。</p>
<p>母校有一些变化,但变化不大,外边加了一座天桥,南门正准备修地铁,以后出行应该会更方便了。最惨的是,作为一名“校外人士”,想买东西,发现支付宝微信一概不能用,只能刷卡。</p>
<p>旅行完毕,是
Latex 错误集锦及使用技巧
http://whatbeg.com/2018/12/17/latexerror.html
2018-12-17T14:24:28.000Z
2018-12-17T14:25:32.163Z
<p>本文记录LaTeX编译,使用过程中的一些错误及其解决方案。</p>
<p>另外,还包括一些使用技巧,常见的元素使用方法等等。</p>
<p>便于自己以及后来人查阅解决。</p>
<p>我是留白。</p>
<p>我是留白。</p>
<h3 id="Latex-“Error-Extra-alignment-tab-has-been-changed-to-cr-“"><a href="#Latex-“Error-Extra-alignment-tab-has-been-changed-to-cr-“" class="headerlink" title="Latex “Error: Extra alignment tab has been changed to \cr. “"></a>Latex “Error: Extra alignment tab has been changed to \cr. “</h3><p>是因为<code>\begin{tabular}</code>后面的参数指定为 A 列,而实际排列了 B 列数据。(A!=B)</p>
<p>解决方案:检查<code>\begin{tabular}</code>后面的<code>r|c|l</code>数量够不够实际列数。</p>
<h3 id="File-ended-while-scanning-use-of-writefile"><a href="#File-ended-while-scanning-use-of-writefile" class="headerlink" title="File ended while scanning use of \@writefile"></a>File ended while scanning use of \@writefile</h3><p><code>.aux</code>不完整,可能是上次编译没通过。</p>
<p>解决方案:删除掉.aux文件,重新编译;如果依然不行,将.tex和其他图像文件、参考文献保留外,由系统编译生成的文件通通删掉,重新编译。</p>
<p>使用技巧</p>
<h3 id="批量注释、取消注释"><a href="#批量注释、取消注释" class="headerlink" title="批量注释、取消注释"></a>批量注释、取消注释</h3><p>Ctrl+Shift+Alt+Right:批量注释</p>
<p>Ctrl+Shift+Alt+Left:取消注释</p>
<h3 id="表格注释"><a href="#表格注释" class="headerlink" title="表格注释"></a>表格注释</h3><figure class="highlight tex"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">\<span class="name">begin</span><span class="string">{table*}</span><span class="string">[htbp]</span></span></span><br><span class="line"> <span class="tag">\<span class="name">caption</span><span class="string">{xx}</span></span></span><br><span class="line"> <span class="tag">\<span class="name">begin</span><span class="string">{center}</span></span></span><br><span class="line"> <span class="tag">\<span class="name">scalebox</span><span class="string">{0.6}</span><span class="string">{</span><br><span class="line"> \begin{tabular}</span><span class="string">{rccccccccccc}</span></span></span><br><span class="line"> <span class="comment">%\toprule</span></span><br><span class="line"> <span class="tag">\<span class="name">hline</span></span></span><br><span class="line"> <span class="tag">\<span class="name">multirow</span><span class="string">{2}</span><span class="string">{*}</span><span class="string">{xx}</span></span> & xx &... <span class="tag">\<span class="name">\</span></span></span><br><span class="line"> ... <span class="tag">\<span class="name">tnote</span><span class="string">{*}</span></span> <span class="tag">\<span class="name">\</span></span></span><br><span class="line"> <span class="comment">%\midrule</span></span><br><span class="line"> <span class="tag">\<span class="name">hline</span></span></span><br><span class="line"> xxx</span><br><span class="line"> <span class="tag">\<span class="name">hline</span></span></span><br><span class="line"> <span class="comment">%\bottomrule</span></span><br><span class="line"> <span class="tag">\<span class="name">end</span><span class="string">{tabular}</span></span></span><br><span class="line"> }</span><br><span class="line"> <span class="tag">\<span class="name">begin</span><span class="string">{tablenotes}</span></span></span><br><span class="line"> <span class="tag">\<span class="name">footnotesize</span></span></span><br><span class="line"> <span class="tag">\<span class="name">item</span><span class="string">[*]</span></span> * the note you wanna add</span><br><span class="line"> <span class="tag">\<span class="name">end</span><span class="string">{tablenotes}</span></span></span><br><span class="line"> <span class="tag">\<span class="name">label</span><span class="string">{xx}</span></span></span><br><span class="line"> <span class="tag">\<span class="name">end</span><span class="string">{center}</span></span></span><br><span class="line"><span class="tag">\<span class="name">end</span><span class="string">{table*}</span></span></span><br></pre></td></tr></table></figure>
<p>本文记录LaTeX编译,使用过程中的一些错误及其解决方案。</p>
<p>另外,还包括一些使用技巧,常见的元素使用方法等等。</p>
<p>便于自己以及后来人查阅解决。</p>
<p>我是留白。</p>
<p>我是留白。</p>
<h3 id="Latex-“Error-E
Scala语法简摘
http://whatbeg.com/2018/12/17/scalagrammar.html
2018-12-17T12:42:02.000Z
2018-12-17T12:47:09.674Z
<p>本文摘录Scala语言的一些语法和关键概念,不成系统,可看做学习笔记罢。</p>
<h2 id="类型推断"><a href="#类型推断" class="headerlink" title="类型推断"></a>类型推断</h2><p><code>for (arg <- args)</code>中<code>arg</code>一定是val类型,循环中不能改变其值。</p>
<p>Scala程序员的平衡感:</p>
<ul>
<li>崇尚val,不可变对象和没有副作用的方法</li>
<li>首先想到他们,只有在特定需要或权衡后才选择var,可变对象或者带副作用方法。</li>
</ul>
<p>Scala 伴生对象,是一个单例对象,可以看做Java中可能用到的静态方法工具类</p>
<h2 id="基本类型"><a href="#基本类型" class="headerlink" title="基本类型"></a>基本类型</h2><p>任何方法都可以是操作符,任何操作符都是方法。</p>
<h2 id="函数式对象"><a href="#函数式对象" class="headerlink" title="函数式对象"></a>函数式对象</h2><p>辅助构造器,关键词this指向当前执行方法被调用的对象实例<br>如果使用在构造器里的话,就是指正在构建的实例</p>
<p>辅助构造器使用<code>def this(..)</code>定义,每个Scala构造器调用终将结束于对主构造器的调用。因为主构造器是类的唯一入口点。</p>
<p>重载操作符,重载后仍然按照原来的优先级,比如* > +</p>
<p>字面量标识符 <code>yield</code>可以作为一个变量/常量名</p>
<h2 id="函数和闭包"><a href="#函数和闭包" class="headerlink" title="函数和闭包"></a>函数和闭包</h2><p>本地函数:函数定义在函数中,本地函数可以随意访问包含它的函数的参数</p>
<p>函数字面量<br>例子: <code>(x: Int) => x + 1</code><br>在<code>foreach</code>,<code>filter</code>等许多函数中会使用到,x的类型往往可以被推断,所以通常也可写成: <code>x => x + 1</code><br>函数字面量存在于源代码,而函数值作为对象存在于运行期。</p>
<p>更简单的,可以使用占位符语法,用下划线当做一个或者多个参数的占位符,只要每个参数在函数字面量内只出现一次即可,第n个下划线代表第n个参数<br>如<code>filter(_ > 0)</code>,调用时,用参数来填补下划线,也即<code>filter(x > 0)</code><br>如<code>reduce(_ + _)</code>,调用时,分别填补,也即<code>reduce(l+r)</code></p>
<p>偏函数(部分应用函数),一个下划线代替所有参数</p>
<p>闭包:函数字面量中包含了自由变量的绑定,运行时必须捕获其绑定。<br>如<code>val addMore = (x: Int) => x + more</code>,<code>more</code>是自由变量<br>注意,闭包是一个非常重要的概念,我们时常会想要在循环体,比如foreach,map中加入一些对外部变量的修改,这是我们在其他语言养成的习惯。<br>直觉上,Scala在运行时会捕获自由变量本身,而不是变量指向的值。<br>比如<br><code>(x: Int) => x + more</code><br>此时创建的闭包可以看到闭包外部对more的改变,同样,闭包对捕获变量做出的修改在闭包外部也可见,比如:<br><figure class="highlight stata"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">val someNumber = <span class="keyword">List</span>(-11, -10, 0, 10)</span><br><span class="line"><span class="keyword">var</span> <span class="keyword">sum</span> = 0</span><br><span class="line">someNumber.<span class="keyword">foreach</span>(x => <span class="keyword">sum</span> += x)</span><br><span class="line"></span><br><span class="line"><span class="keyword">scala</span>> <span class="built_in">sum</span></span><br><span class="line">res: Int = -11</span><br></pre></td></tr></table></figure></p>
<p>但是如果读者用过Spark的话,一定会了解到Spark的闭包和Scala的闭包是不一样的,原因就在于Spark是分布式环境下运行的。<br><a href="http://spark.apache.org/docs/latest/rdd-programming-guide.html#understanding-closures-a-nameclosureslinka" target="_blank" rel="external">这里</a>有Spark官方对closure的描述。</p>
<p>还是上面那个例子<br><figure class="highlight stylus"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-tag">var</span> sum = <span class="number">0</span></span><br><span class="line"><span class="selector-tag">var</span> rdd = sc.parallelize(someNumbers)</span><br><span class="line"></span><br><span class="line"><span class="comment">// Wrong: Don't do this!!</span></span><br><span class="line">rdd.foreach(x => sum += x)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="title">println</span><span class="params">(<span class="string">"Counter value: "</span> + counter)</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">// counter = 0, because driver cannot feel the change of counter</span></span><br></pre></td></tr></table></figure></p>
<p>众所周知,在分布式环境下,rdd的操作形成一个闭包,闭包会先序列化,然后被调度到各个executor执行,且每个executor拿到的其实是序列化后的sum,相当于driver端的一个copy,executor对sum的操作对driver来说不可见,driver的sum对各个executor来说也不可见,所以在driver端,counter始终是0。这就是scala闭包和spark闭包概念的一个最大不同。</p>
<p>重复参数,即可变参数,尾部加<code>*</code>号即可,如:<br><code>def echo(args: String*)</code><br><code>args</code>其实是<code>Array[String]</code>类型,但是仍然不能真的传入一个<code>Array[String]</code>类型的参数,比如要传入<code>arr</code>,你需要<code>echo(arr: _*)</code>这么写,意思是告诉编译器把每个元素当参数而不是把arr当做单一参数。</p>
<p>尾递归:在最后一个动作调用自己的函数。注意只能是单纯的调用自己,不能有多余的表达式,也不能通过其它函数中转</p>
<p>Scala的核心:简洁,简洁,简洁!</p>
<h2 id="控制抽象"><a href="#控制抽象" class="headerlink" title="控制抽象"></a>控制抽象</h2><p>柯里化,传名参数</p>
<h2 id="组合与继承"><a href="#组合与继承" class="headerlink" title="组合与继承"></a>组合与继承</h2><p>组合指一个类持有另一个的引用,借助被引用的类完成任务。</p>
<p>不带参数,且没有副作用的方法可以不写括号</p>
<p>“脆基类”问题:意外的方法重写</p>
<p>多态的重新理解:父类型引用可以指向子类型对象 => 父类对象可有多种形式 => 多态</p>
<p>动态绑定:被调用的实际方法取决于运行期对象基于的类型</p>
<h2 id="Scala-层级"><a href="#Scala-层级" class="headerlink" title="Scala 层级"></a>Scala 层级</h2><p>所有类的父类是Any类,下辖两个子类,AnyRef(所有引用类的父类)和AnyVal(所有值类的父类)<br>底层有Nothing类和Null类,Null类是所有引用类的子类,不兼容子类型,而Nothing是所有类的子类。<br>scala的==对值类型为自然相等,对引用类型来说被视为equals方法的别名,equals初始定义为引用相等,但许多子类都会重写它以实现自然意义上的相等。<br>要比较引用相等,可以使用eq方法(反面是ne方法)</p>
<h2 id="特质-trait"><a href="#特质-trait" class="headerlink" title="特质(trait)"></a>特质(trait)</h2><p>特质类似Java中的接口,混入特质可以使用extends或者with</p>
<p>特质像是带有具体方法的Java接口,并且可以声明字段和维持状态值,特质可以做类定义所能做的事<br>但与类定义有两点不同:<br>1) 特质不能有参数(传递给主构造器)<br>2)super调用时动态绑定的</p>
<p>胖接口:拥有更多方法的接口<br>特质的一个用法就是把瘦接口变成胖接口</p>
<p>需要排序比较时,可以混入(mixin)Ordered特质<br>步骤:混入Ordered特质,实现compare方法,可以自动拥有大多数比较方法,但是不会有equals方法 => 类型擦除</p>
<p>特质的第二个用法:为类提供可堆叠的改变</p>
<p>混入多个特质,最右边的特质最先起作用</p>
<p>不同的组合,不同的次序混入特质,可以依靠少量的特质得到多个不同的类</p>
<p>特质线性化地解释super</p>
<p>特质,用还是不用?<br>1) 如果行为不会被重用,那做成具体类<br>2)如果要在多个不相关的类中重用,那就做成特质<br>3)如果希望从Java代码继承,那就是用抽象类 (只含有抽象成员的scala特质会被直接翻译成Java接口)<br>4)如果计划以编译后的方式发布,或者希望外部组织继承它,更倾向使用抽象类<br>5)如果效率很重要,倾向于使用类</p>
<h2 id="包和引用"><a href="#包和引用" class="headerlink" title="包和引用"></a>包和引用</h2><p><code>_root_</code>顶层包:所有你能写出来的顶层包都是<code>_root_</code>的成员,可以用<code>_root_.yourpack</code>来访问</p>
<p>scala应用灵活在于:<br>1)可以随处import<br>2)可以指对象或包<br>3)可以重命名或者隐藏</p>
<p>每个scala源文件都隐含引用java.lang包,scala包以及单例对象Predef</p>
<p>访问修饰符:protected比Java中的更加严格:仅限子类访问,同一包中的类不能访问</p>
<p>访问修饰符限定规则:</p>
<p><code>private[X] method/class</code> 此类或方法对X下所有类和对象可见</p>
<p><code>protected[X] method/class</code> 对此类或子类或修饰符所在的包,类或对象X可见 (?)</p>
<h2 id="断言和单元测试"><a href="#断言和单元测试" class="headerlink" title="断言和单元测试"></a>断言和单元测试</h2><p><code>assert</code>:<br><code>assert(ele.width === 2)</code> 三等号,如果不等,会报告<code>“3 dit not equal to 2”</code><br>或<br><figure class="highlight stylus"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">expect (<span class="number">2</span>) {</span><br><span class="line"> ele<span class="selector-class">.width</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure></p>
<p>intercept检查是否抛出了期待的异常<br><figure class="highlight stylus"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="title">intercept</span><span class="params">(class[IllegalArgumentException])</span></span> {</span><br><span class="line"> elem(<span class="string">'x'</span>, -<span class="number">2</span>, <span class="number">3</span>)</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p>
<p>scala中的一些测试方法:<br>1) ScalaTest<br>2) Suite:<br>3) JUnit<br>4) TestNG<br>5) Specs 规格测试, a should be … 具有描述部分和规格部分<br>ScalaCheck 属性测试,测试代码具有属性</p>
<h2 id="样例类和模式匹配"><a href="#样例类和模式匹配" class="headerlink" title="样例类和模式匹配"></a>样例类和模式匹配</h2><p>样例类 <code>case class CLSNAME(argA: argAType, ...)</code><br>最大的好处是他们可以支持模式匹配</p>
<p>模式有很多种,包括通配,常量模式,变量模式,构造器模式,序列模式,元组模式,类型模式,更高级的还有变量绑定<br>使用类型模式应注意类型擦除,擦除规则不适用于数组</p>
<p>编译器会为<code>case class</code>自动生成伴生对象<br>编译器也会为该伴生对象自动生成<code>apply</code>,<code>unapply</code>方法</p>
<p>模式守卫</p>
<h2 id="列表:List"><a href="#列表:List" class="headerlink" title="列表:List"></a>列表:List</h2><p>List是协变的,意味着,如果S是T的子类,List[S]就是List[T]的子类,故而 <code>List[Nothing]</code> 是 <code>List[String]</code> 的子类,故可以<code>val List[String] = List()</code></p>
<p><code>::</code> 元素与List连接,元素与元素连接<br><code>:::</code> List与List连接</p>
<p>计算长度<code>.length</code>方法需要遍历整个列表,所以如果判断长度为0的话最好使用<code>.isEmpty</code>方法<br>访问头部:<code>init</code>方法,访问除了最后一个元素外的子列表, <code>head</code>方法:访问第一个元素<br>访问尾部:<code>last</code>方法,访问最后一个元素, <code>tail</code>方法:访问除第一个元素外的列表<br>更一般的,<code>drop</code>, <code>take</code>方法</p>
<p><code>copyToArray</code>: 把列表元素复制到目标数组的一段连续空间<br><code>elements</code>方法:返回迭代器</p>
<p>其他的List高阶方法:<br><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">map</span>,flatMap,foreach</span><br><span class="line">过滤:<span class="built_in">filter</span>,<span class="built_in">partition</span>,<span class="built_in">find</span>,takeWhile,dropWhile,span</span><br><span class="line">论断:forall,exists</span><br><span class="line">折叠:/: 和 :\</span><br><span class="line">翻转<span class="built_in">reverse</span>,排序sortWith</span><br></pre></td></tr></table></figure></p>
<p>List对象的方法:<br><figure class="highlight stylus"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">List<span class="selector-class">.apply</span>,List<span class="selector-class">.range</span>,List.make(<span class="number">4</span>, <span class="string">'a'</span>)</span><br><span class="line">List<span class="selector-class">.unzip</span> 解除啮合</span><br><span class="line">连接: List<span class="selector-class">.flatten</span>, List.concat</span><br></pre></td></tr></table></figure></p>
<p>区别在于前者用列表的列表做参数,后者可以直接用多个列表作为参数(以可变参数的方式)</p>
<p>Scala类型推断<br>Scala采用局部的,基于流的类型推断算法</p>
<p>通常,一旦有需要推断多态方法类型参数的任务时,类型推断器只会参考第一个参数列表中所有的值参数类型,而不会参考之后的参数。<br>库方法设计原则:<br>如果需要把参数设计为若干非函数值即一个函数值的某种多态方法,需要把函数参数独自放在柯里化参数列表的最后面。<br>即,在柯里化方法中,方法类型仅取决于第一段参数。<br>同样的,一种快速解决类型错误问题的方法:<br>添加明确的类型标注</p>
<h2 id="集合与映射"><a href="#集合与映射" class="headerlink" title="集合与映射"></a>集合与映射</h2><figure class="highlight dart"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">Set</span>, Seq, <span class="built_in">Map</span> -> <span class="built_in">Iterable</span></span><br><span class="line">SortedSet</span><br><span class="line">SynchronizedMap</span><br></pre></td></tr></table></figure>
<p>如果元素数量不多,不可变集合比可变集合存储更紧凑,空间更加节省。</p>
<p>可变状态的对象</p>
<p>状态与var变量常常一起出现,但并不具有严格的关系。<br>类即使没有定义或继承var变量,也可以由于把方法调用传递给其他具有可变状态的对象而带有状态,(有点拗口)<br>类即使包含了var变量也可以仍是纯函数的</p>
<p>Scala中,对象的每个非私有的var类型成员变量都隐含定义了<code>getter</code>和<code>setter</code>方法。<br><code>getter</code>方法为<code>x</code><br><code>setter</code>方法为<code>x_</code></p>
<p>类中字段初始化为0/false/null,<br><code>var x = _</code></p>
<p>不可以省略 “= _”,否则var x: Float 为抽象变量,而不是未初始化的变量</p>
<h2 id="类型参数化(重头戏)"><a href="#类型参数化(重头戏)" class="headerlink" title="类型参数化(重头戏)"></a>类型参数化(重头戏)</h2><p>类型参数化让我们能够编写泛型和特质<br>信息隐藏:<br>隐藏主构造器: private加载类名的后面,类参数列表的前面:<br><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Queue</span>[<span class="title">T</span>] <span class="title">private</span> </span>(</span><br><span class="line"> <span class="keyword">private</span> <span class="variable"><span class="keyword">val</span> leading</span>: List[T],</span><br><span class="line"> <span class="keyword">private</span> <span class="variable"><span class="keyword">val</span> tailing</span>: List[T]</span><br><span class="line">)</span><br></pre></td></tr></table></figure></p>
<p>那么如何构造对象呢?</p>
<p>方法1:定义辅助构造器<br><figure class="highlight gradle"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="keyword">this</span>() = <span class="keyword">this</span>(Nil, Nil)</span><br><span class="line"><span class="keyword">def</span> <span class="keyword">this</span>(elem: T*) = <span class="keyword">this</span>(elem.<span class="keyword">toList</span>, Nil)</span><br></pre></td></tr></table></figure></p>
<p>方法2:在伴生对象中编写apply工厂方法<br><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">object Queue {</span><br><span class="line"> def apply[<span class="string">T</span>](<span class="link">xs: T*</span>) = new Queue[<span class="string">T</span>](<span class="link">xs.toList, Nil</span>)</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p>
<p>(注意,scala没有全局方法,方法必须包含在类或对象中)</p>
<p>另一种信息隐藏的方法,直接把类本身通过暴露特质(trait)而隐藏掉</p>
<p>如果类或者特质声明时带类型参数,那么创建变量时也要制定具体的参数化的类型<br>如<br><figure class="highlight lasso"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">trait</span> <span class="built_in">Queue</span><span class="meta">[</span>T<span class="meta">]</span> { .. }</span><br><span class="line">Queue是特质, Queue<span class="meta">[</span><span class="built_in">String</span><span class="meta">]</span>是类型</span><br></pre></td></tr></table></figure></p>
<p>泛型:通过一个能够广泛适用的类或特质,定义了许多特定的类型</p>
<p>在scala中,泛型类默认是非协变的子类型化<br>如果要表明参数的子类型化是协变的,需要变成如下形式:<br><code>trait Queue[+T] { .. }</code></p>
<p>加上-号表示需要逆变的子类型化</p>
<p>只要泛型的参数类型被当做方法参数的类型,那么包含它的类或特质就有可能不能与这个类型参数一起协变<br><figure class="highlight ruby"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Queue</span>[+<span class="title">T</span>] {</span></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">append</span><span class="params">(<span class="symbol">x:</span> T)</span></span> = ...</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p>
<p>下界和上界<br><code>def append[U>: T](x: U) = new Queue[U](leading, x :: tailing)</code><br>语法<code>U >: T</code>定义了T为U的下界,结果U必须是T的超类型,append的参数现在为U而不是T,返回类型也变成了<code>Queue[U]</code>,不过,自身即是自身的超类型又是自身的子类型,所以是类似小于等于的关系。</p>
<p><code>def orderedMergeSort[T <: Ordered[T]](xs: List[T]): List[T] = ...</code><br>语法<code>T <: Ordered[T]</code>定义了类型参数T具有上界<code>Ordered[T]</code>,即传递给<code>orderedMergeSort</code>的参数必须是<code>Ordered[T]</code>的子类型。</p>
<p>本文摘录Scala语言的一些语法和关键概念,不成系统,可看做学习笔记罢。</p>
<h2 id="类型推断"><a href="#类型推断" class="headerlink" title="类型推断"></a>类型推断</h2><p><code>for (arg <
Tensorflow 错误集锦
http://whatbeg.com/2018/12/05/tensorflowtips.html
2018-12-05T15:19:18.000Z
2019-04-16T13:26:41.641Z
<p>本文记录笔者在Tensorflow使用上的一些错误的集锦,方便后来人迅速查阅解决问题。</p>
<p>我是留白。</p>
<p>我是留白。</p>
<h3 id="CreateSession-still-waiting-for-response-from-worker-job-worker-replica-0-task-0"><a href="#CreateSession-still-waiting-for-response-from-worker-job-worker-replica-0-task-0" class="headerlink" title="CreateSession still waiting for response from worker: /job:worker/replica:0/task:0"></a>CreateSession still waiting for response from worker: /job:worker/replica:0/task:0</h3><figure class="highlight groovy"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="number">2018</span><span class="number">-12</span><span class="number">-05</span> <span class="number">22</span>:<span class="number">18</span>:<span class="number">24.565303</span>: I tensorflow<span class="regexp">/core/</span>distributed_runtime<span class="regexp">/rpc/</span>grpc_channel.<span class="string">cc:</span><span class="number">222</span>] Initialize GrpcChannelCache <span class="keyword">for</span> job ps -> {<span class="number">0</span> -> <span class="string">localhost:</span><span class="number">3376</span>}</span><br><span class="line"><span class="number">2018</span><span class="number">-12</span><span class="number">-05</span> <span class="number">22</span>:<span class="number">18</span>:<span class="number">24.565372</span>: I tensorflow<span class="regexp">/core/</span>distributed_runtime<span class="regexp">/rpc/</span>grpc_channel.<span class="string">cc:</span><span class="number">222</span>] Initialize GrpcChannelCache <span class="keyword">for</span> job worker -> {<span class="number">0</span> -> <span class="string">localhost:</span><span class="number">3330</span>, <span class="number">1</span> -> <span class="string">localhost:</span><span class="number">3331</span>}</span><br><span class="line"><span class="number">2018</span><span class="number">-12</span><span class="number">-05</span> <span class="number">22</span>:<span class="number">18</span>:<span class="number">24.569212</span>: I tensorflow<span class="regexp">/core/</span>distributed_runtime<span class="regexp">/rpc/</span>grpc_server_lib.<span class="string">cc:</span><span class="number">381</span>] Started server with <span class="string">target:</span> <span class="string">grpc:</span><span class="comment">//localhost:3376</span></span><br><span class="line"><span class="number">2018</span><span class="number">-12</span><span class="number">-05</span> <span class="number">22</span>:<span class="number">18</span>:<span class="number">26.170901</span>: I tensorflow<span class="regexp">/core/</span>distributed_runtime<span class="regexp">/rpc/</span>grpc_channel.<span class="string">cc:</span><span class="number">222</span>] Initialize GrpcChannelCache <span class="keyword">for</span> job ps -> {<span class="number">0</span> -> <span class="string">localhost:</span><span class="number">3376</span>}</span><br><span class="line"><span class="number">2018</span><span class="number">-12</span><span class="number">-05</span> <span class="number">22</span>:<span class="number">18</span>:<span class="number">26.170969</span>: I tensorflow<span class="regexp">/core/</span>distributed_runtime<span class="regexp">/rpc/</span>grpc_channel.<span class="string">cc:</span><span class="number">222</span>] Initialize GrpcChannelCache <span class="keyword">for</span> job worker -> {<span class="number">0</span> -> <span class="string">localhost:</span><span class="number">3330</span>, <span class="number">1</span> -> <span class="string">localhost:</span><span class="number">3331</span>}</span><br><span class="line"><span class="number">2018</span><span class="number">-12</span><span class="number">-05</span> <span class="number">22</span>:<span class="number">18</span>:<span class="number">26.174856</span>: I tensorflow<span class="regexp">/core/</span>distributed_runtime<span class="regexp">/rpc/</span>grpc_server_lib.<span class="string">cc:</span><span class="number">381</span>] Started server with <span class="string">target:</span> <span class="string">grpc:</span><span class="comment">//localhost:3330</span></span><br><span class="line"><span class="number">2018</span><span class="number">-12</span><span class="number">-05</span> <span class="number">22</span>:<span class="number">18</span>:<span class="number">27.177003</span>: I tensorflow<span class="regexp">/core/</span>distributed_runtime<span class="regexp">/rpc/</span>grpc_channel.<span class="string">cc:</span><span class="number">222</span>] Initialize GrpcChannelCache <span class="keyword">for</span> job ps -> {<span class="number">0</span> -> <span class="string">localhost:</span><span class="number">3376</span>}</span><br><span class="line"><span class="number">2018</span><span class="number">-12</span><span class="number">-05</span> <span class="number">22</span>:<span class="number">18</span>:<span class="number">27.177071</span>: I tensorflow<span class="regexp">/core/</span>distributed_runtime<span class="regexp">/rpc/</span>grpc_channel.<span class="string">cc:</span><span class="number">222</span>] Initialize GrpcChannelCache <span class="keyword">for</span> job worker -> {<span class="number">0</span> -> <span class="string">localhost:</span><span class="number">3330</span>, <span class="number">1</span> -> <span class="string">localhost:</span><span class="number">3331</span>}</span><br><span class="line"><span class="number">2018</span><span class="number">-12</span><span class="number">-05</span> <span class="number">22</span>:<span class="number">18</span>:<span class="number">27.180980</span>: I tensorflow<span class="regexp">/core/</span>distributed_runtime<span class="regexp">/rpc/</span>grpc_server_lib.<span class="string">cc:</span><span class="number">381</span>] Started server with <span class="string">target:</span> <span class="string">grpc:</span><span class="comment">//localhost:3331</span></span><br><span class="line"><span class="number">2018</span><span class="number">-12</span><span class="number">-05</span> <span class="number">22</span>:<span class="number">18</span>:<span class="number">34.625459</span>: I tensorflow<span class="regexp">/core/</span>distributed_runtime<span class="regexp">/master.cc:267] CreateSession still waiting for response from worker: /</span><span class="string">job:</span>worker<span class="regexp">/replica:0/</span><span class="string">task:</span><span class="number">0</span></span><br><span class="line"><span class="number">2018</span><span class="number">-12</span><span class="number">-05</span> <span class="number">22</span>:<span class="number">18</span>:<span class="number">34.625513</span>: I tensorflow<span class="regexp">/core/</span>distributed_runtime<span class="regexp">/master.cc:267] CreateSession still waiting for response from worker: /</span><span class="string">job:</span>worker<span class="regexp">/replica:0/</span><span class="string">task:</span><span class="number">1</span></span><br><span class="line"><span class="number">2018</span><span class="number">-12</span><span class="number">-05</span> <span class="number">22</span>:<span class="number">18</span>:<span class="number">36.231936</span>: I tensorflow<span class="regexp">/core/</span>distributed_runtime<span class="regexp">/master.cc:267] CreateSession still waiting for response from worker: /</span><span class="string">job:</span>ps<span class="regexp">/replica:0/</span><span class="string">task:</span><span class="number">0</span></span><br><span class="line"><span class="number">2018</span><span class="number">-12</span><span class="number">-05</span> <span class="number">22</span>:<span class="number">18</span>:<span class="number">36.231971</span>: I tensorflow<span class="regexp">/core/</span>distributed_runtime<span class="regexp">/master.cc:267] CreateSession still waiting for response from worker: /</span><span class="string">job:</span>worker<span class="regexp">/replica:0/</span><span class="string">task:</span><span class="number">1</span></span><br><span class="line"><span class="number">2018</span><span class="number">-12</span><span class="number">-05</span> <span class="number">22</span>:<span class="number">18</span>:<span class="number">37.235899</span>: I tensorflow<span class="regexp">/core/</span>distributed_runtime<span class="regexp">/master.cc:267] CreateSession still waiting for response from worker: /</span><span class="string">job:</span>ps<span class="regexp">/replica:0/</span><span class="string">task:</span><span class="number">0</span></span><br><span class="line"><span class="number">2018</span><span class="number">-12</span><span class="number">-05</span> <span class="number">22</span>:<span class="number">18</span>:<span class="number">37.235952</span>: I tensorflow<span class="regexp">/core/</span>distributed_runtime<span class="regexp">/master.cc:267] CreateSession still waiting for response from worker: /</span><span class="string">job:</span>worker<span class="regexp">/replica:0/</span><span class="string">task:</span><span class="number">0</span></span><br></pre></td></tr></table></figure>
<p>首先保证<code>job_name,task_index,ps_hosts,worker_hosts</code>这四个参数都是正确的,考虑以下这种情况是不正确的:<br>在一个IP为192.168.1.100的机器上启动ps或worker进程:<br><figure class="highlight ini"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">--job_name</span>=worker</span><br><span class="line"><span class="attr">--task_index</span>=<span class="number">1</span></span><br><span class="line"><span class="attr">--ps_hosts</span>=<span class="number">192.168</span>.<span class="number">1.100</span>:<span class="number">2222</span>,<span class="number">192.168</span>.<span class="number">1.101</span>:<span class="number">2222</span></span><br><span class="line"><span class="attr">--worker_hosts</span>=<span class="number">192.168</span>.<span class="number">1.100</span>:<span class="number">2223</span>,<span class="number">192.168</span>.<span class="number">1.101</span>:<span class="number">2223</span></span><br></pre></td></tr></table></figure></p>
<p>因为该进程启动位置是192.168.1.100,但是运行参数中指定的task_index为1,对应的IP地址是ps_hosts或worker_hosts的第二项(第一项的task_index为0),也就是192.168.1.101,和进程本身所在机器的IP不一致。</p>
<p>另外一种情况也会导致该问题的发生,从TensorFlow-1.4开始,分布式会自动使用环境变量中的代理去连接,如果运行的节点之间不需要代理互连,那么将代理的环境变量移除即可,在脚本的开始位置添加代码:<br>注意这段代码必须写在import tensorflow as tf或者import moxing.tensorflow as mox之前<br><figure class="highlight stylus"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">import os</span><br><span class="line">os<span class="selector-class">.enrivon</span><span class="selector-class">.pop</span>(<span class="string">'http_proxy'</span>)</span><br><span class="line">os<span class="selector-class">.enrivon</span><span class="selector-class">.pop</span>(<span class="string">'https_proxy'</span>)</span><br></pre></td></tr></table></figure></p>
<p>— 摘自(<a href="https://bbs.huaweicloud.com/blogs/463145f7a1d111e89fc57ca23e93a89f" target="_blank" rel="external">https://bbs.huaweicloud.com/blogs/463145f7a1d111e89fc57ca23e93a89f</a>)</p>
<h3 id="ImportError-lib64-libstdc-so-6-version-CXXABI-1-3-9’-not-found"><a href="#ImportError-lib64-libstdc-so-6-version-CXXABI-1-3-9’-not-found" class="headerlink" title="ImportError: /lib64/libstdc++.so.6: version `CXXABI_1.3.9’ not found"></a>ImportError: /lib64/libstdc++.so.6: version `CXXABI_1.3.9’ not found</h3><figure class="highlight qml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">/home/experiment/huqiu/anaconda3/lib/python3<span class="number">.6</span>/site-packages/h5py/<span class="attribute">__init__.py</span>:<span class="number">36</span>: <span class="attribute">FutureWarning</span>: Conversion <span class="keyword">of</span> the second argument <span class="keyword">of</span> issubdtype from <span class="string">`float`</span> to <span class="string">`np.floating`</span> is deprecated. In future, it will be treated <span class="keyword">as</span> <span class="string">`np.float64 == np.dtype(float).type`</span>.</span><br><span class="line"> from ._conv <span class="keyword">import</span><span class="string"> register_converters as _register_converters</span></span><br><span class="line">Traceback (most recent call last):</span><br><span class="line"> File <span class="string">"trainer.py"</span>, line <span class="number">14</span>, <span class="keyword">in</span> <<span class="built_in">module</span>></span><br><span class="line"> <span class="keyword">import</span><span class="string"> sklearn.datasets</span></span><br><span class="line"> File <span class="string">"/home/experiment/huqiu/anaconda3/lib/python3.6/site-packages/sklearn/__init__.py"</span>, line <span class="number">134</span>, <span class="keyword">in</span> <<span class="built_in">module</span>></span><br><span class="line"> from .base <span class="keyword">import</span><span class="string"> clone</span></span><br><span class="line"> File <span class="string">"/home/experiment/huqiu/anaconda3/lib/python3.6/site-packages/sklearn/base.py"</span>, line <span class="number">11</span>, <span class="keyword">in</span> <<span class="built_in">module</span>></span><br><span class="line"> from scipy <span class="keyword">import</span><span class="string"> sparse</span></span><br><span class="line"> File <span class="string">"/home/experiment/huqiu/anaconda3/lib/python3.6/site-packages/scipy/sparse/__init__.py"</span>, line <span class="number">229</span>, <span class="keyword">in</span> <<span class="built_in">module</span>></span><br><span class="line"> from .csr <span class="keyword">import</span><span class="string"> *</span></span><br><span class="line"> File <span class="string">"/home/experiment/huqiu/anaconda3/lib/python3.6/site-packages/scipy/sparse/csr.py"</span>, line <span class="number">15</span>, <span class="keyword">in</span> <<span class="built_in">module</span>></span><br><span class="line"> from ._sparsetools <span class="keyword">import</span><span class="string"> csr_tocsc, csr_tobsr, csr_count_blocks, \</span></span><br><span class="line"><span class="attribute">ImportError</span>: /lib64/libstdc++.so<span class="number">.6</span>: version <span class="string">`CXXABI_1.3.9' not found (required by /home/experiment/huqiu/anaconda3/lib/python3.6/site-packages/scipy/sparse/_sparsetools.cpython-36m-x86_64-linux-gnu.so)</span></span><br></pre></td></tr></table></figure>
<p>系统的库文件较老,不含CXXABI_1.3.9,可将<code><Anaconda_PATH>/lib</code>加入<code>LD_LIBRARY_PATH</code>中,像这样:<br><figure class="highlight crystal"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">export LD_LIBRARY_PATH=<span class="regexp">/home/</span>../anaconda3/<span class="class"><span class="keyword">lib</span>:$<span class="title">LD_LIBRARY_PATH</span></span></span><br></pre></td></tr></table></figure></p>
<p>如此,系统会先找到anaconda里面的lib,从而满足要求。</p>
<p>参考:Stackoverflow. <a href="https://stackoverflow.com/questions/49875588/importerror-lib64-libstdc-so-6-version-cxxabi-1-3-9-not-found" target="_blank" rel="external">问题2</a></p>
<h3 id="分布式Tensorflow-ps端运行出现tensorflow-python-framework-errors-impl-UnavailableError-OS-Error"><a href="#分布式Tensorflow-ps端运行出现tensorflow-python-framework-errors-impl-UnavailableError-OS-Error" class="headerlink" title="分布式Tensorflow, ps端运行出现tensorflow.python.framework.errors_impl.UnavailableError: OS Error"></a>分布式Tensorflow, ps端运行出现tensorflow.python.framework.errors_impl.UnavailableError: OS Error</h3><figure class="highlight stata"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line">2018-12-07 15:40:05.167922: I tensorflow/core/distributed_runtime/rpc/grpc_channel.<span class="keyword">cc</span>:222] Initialize GrpcChannelCache <span class="keyword">for</span> job ps -> {0 -> localhost:3333}</span><br><span class="line">2018-12-07 15:40:05.167970: I tensorflow/core/distributed_runtime/rpc/grpc_channel.<span class="keyword">cc</span>:222] Initialize GrpcChannelCache <span class="keyword">for</span> job worker -> {0 -> 192.168.100.36:3333, 1 -> 192.168.100.37:3333}</span><br><span class="line">2018-12-07 15:40:05.171857: I tensorflow/core/distributed_runtime/rpc/grpc_server_lib.<span class="keyword">cc</span>:381] Started server with target: grpc:<span class="comment">//localhost:3333</span></span><br><span class="line">Parameter server: waiting <span class="keyword">for</span> <span class="keyword">cluster</span> connection...</span><br><span class="line">2018-12-07 15:40:05.213496: <span class="keyword">E</span> tensorflow/core/distributed_runtime/master.<span class="keyword">cc</span>:315] CreateSession failed because worker /job:worker/replica:0/task:0 returned <span class="keyword">error</span>: Unavailable: OS <span class="keyword">Error</span></span><br><span class="line">2018-12-07 15:40:05.213645: <span class="keyword">E</span> tensorflow/core/distributed_runtime/master.<span class="keyword">cc</span>:315] CreateSession failed because worker /job:worker/replica:0/task:1 returned <span class="keyword">error</span>: Unavailable: OS <span class="keyword">Error</span></span><br><span class="line">Traceback (most recent call last):</span><br><span class="line"> <span class="keyword">File</span> <span class="string">"/home/experiment/huqiu/anaconda3/lib/python3.6/site-packages/tensorflow/python/client/session.py"</span>, <span class="keyword">line</span> 1334, <span class="keyword">in</span> _do_call</span><br><span class="line"> <span class="keyword">return</span> fn(*<span class="keyword">args</span>)</span><br><span class="line"> <span class="keyword">File</span> <span class="string">"/home/experiment/huqiu/anaconda3/lib/python3.6/site-packages/tensorflow/python/client/session.py"</span>, <span class="keyword">line</span> 1317, <span class="keyword">in</span> _run_fn</span><br><span class="line"> self._extend_graph()</span><br><span class="line"> <span class="keyword">File</span> <span class="string">"/home/experiment/huqiu/anaconda3/lib/python3.6/site-packages/tensorflow/python/client/session.py"</span>, <span class="keyword">line</span> 1352, <span class="keyword">in</span> _extend_graph</span><br><span class="line"> tf_session.ExtendSession(self._session)</span><br><span class="line">tensorflow.python.framework.errors_impl.UnavailableError: OS <span class="keyword">Error</span></span><br><span class="line"></span><br><span class="line">During handling of the above exception, another exception occurred:</span><br><span class="line"></span><br><span class="line">Traceback (most recent call last):</span><br><span class="line"> <span class="keyword">File</span> <span class="string">"trainer.py"</span>, <span class="keyword">line</span> 364, <span class="keyword">in</span> <module></span><br><span class="line"> tf.<span class="keyword">app</span>.<span class="keyword">run</span>(main=main, argv=[sys.argv[0]] + unparsed)</span><br><span class="line"> <span class="keyword">File</span> <span class="string">"/home/experiment/huqiu/anaconda3/lib/python3.6/site-packages/tensorflow/python/platform/app.py"</span>, <span class="keyword">line</span> 125, <span class="keyword">in</span> <span class="keyword">run</span></span><br><span class="line"> _sys.<span class="keyword">exit</span>(main(argv))</span><br><span class="line"> <span class="keyword">File</span> <span class="string">"trainer.py"</span>, <span class="keyword">line</span> 70, <span class="keyword">in</span> main</span><br><span class="line"> num_classes=num_classes)</span><br><span class="line"> <span class="keyword">File</span> <span class="string">"trainer.py"</span>, <span class="keyword">line</span> 138, <span class="keyword">in</span> parameter_server</span><br><span class="line"> sess.<span class="keyword">run</span>(tf.report_uninitialized_variables())</span><br><span class="line"> <span class="keyword">File</span> <span class="string">"/home/experiment/huqiu/anaconda3/lib/python3.6/site-packages/tensorflow/python/client/session.py"</span>, <span class="keyword">line</span> 929, <span class="keyword">in</span> <span class="keyword">run</span></span><br><span class="line"> run_metadata_ptr)</span><br><span class="line"> <span class="keyword">File</span> <span class="string">"/home/experiment/huqiu/anaconda3/lib/python3.6/site-packages/tensorflow/python/client/session.py"</span>, <span class="keyword">line</span> 1152, <span class="keyword">in</span> _run</span><br><span class="line"> feed_dict_tensor, options, run_metadata)</span><br><span class="line"> <span class="keyword">File</span> <span class="string">"/home/experiment/huqiu/anaconda3/lib/python3.6/site-packages/tensorflow/python/client/session.py"</span>, <span class="keyword">line</span> 1328, <span class="keyword">in</span> _do_run</span><br><span class="line"> run_metadata)</span><br><span class="line"> <span class="keyword">File</span> <span class="string">"/home/experiment/huqiu/anaconda3/lib/python3.6/site-packages/tensorflow/python/client/session.py"</span>, <span class="keyword">line</span> 1348, <span class="keyword">in</span> _do_call</span><br><span class="line"> raise <span class="keyword">type</span>(<span class="keyword">e</span>)(node_def, op, message)</span><br><span class="line">tensorflow.python.framework.errors_impl.UnavailableError: OS <span class="keyword">Error</span></span><br></pre></td></tr></table></figure>
<p>如上所示,运行多机分布式 tensorflow 的 parameter server 进程时,出现这个错误。<br><a href="https://github.com/tensorflow/tensorflow/issues/17852#issuecomment-414470314" target="_blank" rel="external">这里</a>说道:</p>
<blockquote>
<p>This has been troubling me for a while. I found out that the problem<br>is that GRPC uses the native “epoll” polling engine for communication.<br>Changing this to a portable polling engine solved this issue for me.<br>The way to do is to set the environment variable,<br>“GRPC_POLL_STRATEGY=poll” before running the tensorflow programs. This<br>solved this issue for me. For reference, see,<br><a href="https://github.com/grpc/grpc/blob/master/doc/environment_variables.md" target="_blank" rel="external">https://github.com/grpc/grpc/blob/master/doc/environment_variables.md</a>.</p>
</blockquote>
<p>按照其所属,在环境变量中新增一条:<br><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">export</span> GRPC_POLL_STRATEGY=<span class="literal">poll</span></span><br></pre></td></tr></table></figure></p>
<p>成功解决问题。</p>
<h2 id="参考文献"><a href="#参考文献" class="headerlink" title="参考文献"></a>参考文献</h2><ul>
<li><a href="https://bbs.huaweicloud.com/blogs/463145f7a1d111e89fc57ca23e93a89f" target="_blank" rel="external">问题1</a></li>
<li><a href="https://github.com/shenqixiaojiang/distributeTensorflow/issues/2" target="_blank" rel="external">问题1</a></li>
</ul>
<p>本文记录笔者在Tensorflow使用上的一些错误的集锦,方便后来人迅速查阅解决问题。</p>
<p>我是留白。</p>
<p>我是留白。</p>
<h3 id="CreateSession-still-waiting-for-response-from-worker-jo
远程访问二跳节点的Jupyter Notebook
http://whatbeg.com/2018/12/05/jupyternotebook-1.html
2018-12-05T15:13:49.000Z
2018-12-08T15:10:31.452Z
<p>用 Jupyter Notebook 运行 Python 程序时,本机有些吃力,于是想转向集群的服务器来运行。</p>
<p>可是本机和集群服务器之间还有一个跳板机,横加阻隔,较为麻烦,最终经过一段时间的摸索,决定采用两边各进一步的方法:服务端 jupyter notebook 远程访问 + 本机端端口转发。</p>
<p>为便于说明,设本机为A,跳板机B,目标运行服务器C。我们的 notebook 将在C上运行。</p>
<p>首先配置C上的notebook,使得可以被内网别的机器(如B)直接访问。</p>
<figure class="highlight elixir"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="number">1</span>. 登陆远程服务器</span><br><span class="line"><span class="number">2</span>.生成配置文件</span><br><span class="line"><span class="variable">$ </span>jupyter notebook --generate-config</span><br><span class="line"></span><br><span class="line"><span class="number">3</span>. 修改默认配置文件</span><br><span class="line"><span class="variable">$ </span>vim ~<span class="regexp">/.jupyter/jupyter</span>_notebook_config.py</span><br><span class="line">进行如下修改:</span><br><span class="line"></span><br><span class="line">c.NotebookApp.ip = <span class="string">'0.0.0.0'</span> <span class="comment"># 支持其它IP访问,关键</span></span><br><span class="line">c.NotebookApp.port = <span class="number">8888</span> <span class="comment"># Jupyter Notebook 惯用端口</span></span><br><span class="line"></span><br><span class="line"><span class="number">4</span>. 启动jupyter notebook:</span><br><span class="line"><span class="variable">$ </span>jupyter notebook</span><br></pre></td></tr></table></figure>
<p>启动后如下图所示,<br><img src="https://gitee.com/whyseek/blogimages/raw/master/jupyter_notebook.png" alt=""></p>
<p>记住图中的<code>http://0.0.0.0:8888/?token</code>后面的token, 后面认证需要用。</p>
<p>这样我们第一步就做完了,这时,在跳板机B上可以随便访问C的8888端口,即其jupyter notebook。</p>
<p>第二部就是建立端口转发,使得我们在本地浏览器中输入<code>localhost:xxxx</code>即可以经过跳板机访问<code>C:8888</code>。</p>
<p>通过ssh可以建立本地转发。<br><figure class="highlight elixir"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ssh -f -NL <span class="number">16888</span><span class="symbol">:<C_ip></span><span class="symbol">:</span><span class="number">8888</span> [username@]<B_ip></span><br></pre></td></tr></table></figure></p>
<p>这样意味着我们访问<code>localhost:16888</code>端口时可以通过B转发到<c_ip>:8888,也即我们可以访问C的notebook啦。</c_ip></p>
<p>最开始的时候会让输入密码,在最下面输入刚说的token,然后设置新密码即可。</p>
<p>用 Jupyter Notebook 运行 Python 程序时,本机有些吃力,于是想转向集群的服务器来运行。</p>
<p>可是本机和集群服务器之间还有一个跳板机,横加阻隔,较为麻烦,最终经过一段时间的摸索,决定采用两边各进一步的方法:服务端 jupyter notebo
CentOS 7 卸载CUDA 9.1 安装CUDA8.0 并安装Tensorflow GPU版
http://whatbeg.com/2018/03/17/cudainstall.html
2018-03-17T08:46:42.000Z
2018-03-18T06:31:26.936Z
<p>事前各软件版本:<br>NVIDIA驱动:390.25<br>CUDA: 9.1</p>
<p>现在Tensorflow不支持CUDA 9.1,所以采用降级的办法来解决,将CUDA降为8.0,由于NVIDIA驱动可以向下兼容,所以不用卸载NVIDIA驱动。当然也可以不卸载9.1,但是安装目录下cuda软连接指向cuda-8.0即可。</p>
<h2 id="卸载CUDA-9-1-(可选)"><a href="#卸载CUDA-9-1-(可选)" class="headerlink" title="卸载CUDA 9.1 (可选)"></a>卸载CUDA 9.1 (可选)</h2><figure class="highlight stata"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">cd</span> /usr/<span class="keyword">local</span>/cuda-9.1/bin</span><br><span class="line">sudo ./uninstall_cuda_toolkit_9.1.<span class="keyword">pl</span></span><br></pre></td></tr></table></figure>
<h2 id="安装CUDA-8-0"><a href="#安装CUDA-8-0" class="headerlink" title="安装CUDA 8.0"></a>安装CUDA 8.0</h2><p>从官网下载<a href="https://developer.nvidia.com/cuda-80-ga2-download-archive" target="_blank" rel="external">CUDA 8.0 ToolKit</a>。<br>这里我下载的是<code>cuda_8.0.61_375.26_linux.run</code>,并下载补丁<code>cuda_8.0.61.2_linux.run</code>。</p>
<p>进入root用户,将上述两个文件拷贝到<code>/root</code>(或其他地方),直接用root用户较方便。</p>
<figure class="highlight applescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">chmod +x cuda_8<span class="number">.0</span><span class="number">.61</span>* <span class="comment"># 加上执行权限</span></span><br><span class="line">./cuda_8<span class="number">.0</span><span class="number">.61</span>_375<span class="number">.26</span>_linux.<span class="built_in">run</span></span><br><span class="line">第一个问题,是否安装NVIDIA驱动,选n(不安装)</span><br><span class="line">其他问题自己决定,默认装到/usr/<span class="keyword">local</span>即可</span><br></pre></td></tr></table></figure>
<p>然后可以到Samples目录,先make编译,然后到bin下找到<code>deviceQuery</code>,执行<code>./deviceQuery</code>,<br>如果安装成功应该会显示类似如下信息:<br><figure class="highlight nix"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">...(省略)</span><br><span class="line">deviceQuery, CUDA <span class="attr">Driver</span> = CUDART, CUDA Driver <span class="attr">Version</span> = <span class="number">9.1</span>, CUDA Runtime <span class="attr">Version</span> = <span class="number">8.0</span>, <span class="attr">NumDevs</span> = <span class="number">2</span>, <span class="attr">Device0</span> = Tesla K80, <span class="attr">Device0</span> = Tesla K80</span><br><span class="line"><span class="attr">Result</span> = PASS</span><br></pre></td></tr></table></figure></p>
<h2 id="安装cuDNN"><a href="#安装cuDNN" class="headerlink" title="安装cuDNN"></a>安装cuDNN</h2><p>cuDNN是NVIDIA专为Deep Learning应用开发的支持库。<br>我们打算安装Tensorflow 1.4.0,该版本要求<code>libcudnn.so.6</code>,所以下载v6版本的cuDNN。<br>到<a href="https://developer.nvidia.com/rdp/cudnn-download" target="_blank" rel="external">这里下载</a></p>
<p>下载:<code>cudnn-8.0-linux-x64-v6.0.tgz</code>。</p>
<p>将其传到<code>/usr/local</code>目录下,然后解压即可:<br><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-tag">tar</span> <span class="selector-tag">-zxvf</span> <span class="selector-tag">cudnn-8</span><span class="selector-class">.0-linux-x64-v6</span><span class="selector-class">.0</span><span class="selector-class">.tgz</span></span><br></pre></td></tr></table></figure></p>
<p>这样就成功安装了CUDA 8.0这一套,但是驱动仍然用的高版本驱动390.25,不过应该没关系的吧。</p>
<h2 id="添加环境变量"><a href="#添加环境变量" class="headerlink" title="添加环境变量"></a>添加环境变量</h2><figure class="highlight xquery"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">vim ~/.bashrc</span><br><span class="line">添加:</span><br><span class="line">export LD_LIBRARY_PATH=/usr/local/cuda-<span class="number">8.0</span>/lib64:/usr/local/cuda-<span class="number">8.0</span>/extras/CUPTI/lib64:$LD_LIBRARY_PATH</span><br><span class="line">export CUDA_HOME=/usr/local/cuda-<span class="number">8.0</span></span><br><span class="line">export PATH=$CUDA_HOME/bin:$PATH</span><br><span class="line"></span><br><span class="line">source .bashrc</span><br></pre></td></tr></table></figure>
<h2 id="安装Tensorflow"><a href="#安装Tensorflow" class="headerlink" title="安装Tensorflow"></a>安装Tensorflow</h2><figure class="highlight cmake"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pip <span class="keyword">install</span> tensorflow-gpu==<span class="number">1.4</span>.<span class="number">0</span></span><br></pre></td></tr></table></figure>
<p>试一把,import tensorflow成功即说明CUDA,cuDNN安装完成,且版本没问题。</p>
<p>运行两个GPU的例子:<br><figure class="highlight livecodeserver"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">import tensorflow <span class="keyword">as</span> tf</span><br><span class="line">c = []</span><br><span class="line"><span class="keyword">for</span> d <span class="keyword">in</span> [<span class="string">'/device:GPU:0'</span>, <span class="string">'/device:GPU:1'</span>]:</span><br><span class="line"> <span class="keyword">with</span> tf.device(d):</span><br><span class="line"> <span class="keyword">a</span> = tf.<span class="built_in">constant</span>([<span class="number">1.0</span>, <span class="number">2.0</span>, <span class="number">3.0</span>, <span class="number">4.0</span>, <span class="number">5.0</span>, <span class="number">6.0</span>], shape=[<span class="number">2</span>, <span class="number">3</span>])</span><br><span class="line"> b = tf.<span class="built_in">constant</span>([<span class="number">1.0</span>, <span class="number">2.0</span>, <span class="number">3.0</span>, <span class="number">4.0</span>, <span class="number">5.0</span>, <span class="number">6.0</span>], shape=[<span class="number">3</span>, <span class="number">2</span>])</span><br><span class="line"> c.append(tf.matmul(<span class="keyword">a</span>, b))</span><br><span class="line"><span class="keyword">with</span> tf.device(<span class="string">'/cpu:0'</span>):</span><br><span class="line"> <span class="built_in">sum</span> = tf.add_n(c)</span><br><span class="line"><span class="comment"># Creates a session with log_device_placement set to True.</span></span><br><span class="line">sess = tf.Session(config=tf.ConfigProto(log_device_placement=True))</span><br><span class="line"><span class="comment"># Runs the op.</span></span><br><span class="line">print(sess.run(<span class="built_in">sum</span>))</span><br></pre></td></tr></table></figure></p>
<p>输出:<br><figure class="highlight groovy"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">MatMul_1:</span> (MatMul): <span class="regexp">/job:localhost/</span><span class="string">replica:</span><span class="number">0</span><span class="regexp">/task:0/</span><span class="string">device:</span><span class="string">GPU:</span><span class="number">1</span></span><br><span class="line"><span class="number">2018</span><span class="number">-03</span><span class="number">-12</span> <span class="number">19</span>:<span class="number">22</span>:<span class="number">00.759031</span>: I tensorflow<span class="regexp">/core/</span>common_runtime<span class="regexp">/placer.cc:874] MatMul_1: (MatMul)/</span><span class="string">job:</span>localhost<span class="regexp">/replica:0/</span><span class="string">task:</span><span class="number">0</span>/<span class="string">device:</span><span class="string">GPU:</span><span class="number">1</span></span><br><span class="line"><span class="string">MatMul:</span> (MatMul): <span class="regexp">/job:localhost/</span><span class="string">replica:</span><span class="number">0</span><span class="regexp">/task:0/</span><span class="string">device:</span><span class="string">GPU:</span><span class="number">0</span></span><br><span class="line"><span class="number">2018</span><span class="number">-03</span><span class="number">-12</span> <span class="number">19</span>:<span class="number">22</span>:<span class="number">00.759079</span>: I tensorflow<span class="regexp">/core/</span>common_runtime<span class="regexp">/placer.cc:874] MatMul: (MatMul)/</span><span class="string">job:</span>localhost<span class="regexp">/replica:0/</span><span class="string">task:</span><span class="number">0</span>/<span class="string">device:</span><span class="string">GPU:</span><span class="number">0</span></span><br><span class="line"><span class="string">AddN:</span> (AddN): <span class="regexp">/job:localhost/</span><span class="string">replica:</span><span class="number">0</span><span class="regexp">/task:0/</span><span class="string">device:</span><span class="string">CPU:</span><span class="number">0</span></span><br><span class="line"><span class="number">2018</span><span class="number">-03</span><span class="number">-12</span> <span class="number">19</span>:<span class="number">22</span>:<span class="number">00.759100</span>: I tensorflow<span class="regexp">/core/</span>common_runtime<span class="regexp">/placer.cc:874] AddN: (AddN)/</span><span class="string">job:</span>localhost<span class="regexp">/replica:0/</span><span class="string">task:</span><span class="number">0</span>/<span class="string">device:</span><span class="string">CPU:</span><span class="number">0</span></span><br><span class="line"><span class="string">Const_3:</span> (Const): <span class="regexp">/job:localhost/</span><span class="string">replica:</span><span class="number">0</span><span class="regexp">/task:0/</span><span class="string">device:</span><span class="string">GPU:</span><span class="number">1</span></span><br><span class="line"><span class="number">2018</span><span class="number">-03</span><span class="number">-12</span> <span class="number">19</span>:<span class="number">22</span>:<span class="number">00.759126</span>: I tensorflow<span class="regexp">/core/</span>common_runtime<span class="regexp">/placer.cc:874] Const_3: (Const)/</span><span class="string">job:</span>localhost<span class="regexp">/replica:0/</span><span class="string">task:</span><span class="number">0</span>/<span class="string">device:</span><span class="string">GPU:</span><span class="number">1</span></span><br><span class="line"><span class="string">Const_2:</span> (Const): <span class="regexp">/job:localhost/</span><span class="string">replica:</span><span class="number">0</span><span class="regexp">/task:0/</span><span class="string">device:</span><span class="string">GPU:</span><span class="number">1</span></span><br><span class="line"><span class="number">2018</span><span class="number">-03</span><span class="number">-12</span> <span class="number">19</span>:<span class="number">22</span>:<span class="number">00.759145</span>: I tensorflow<span class="regexp">/core/</span>common_runtime<span class="regexp">/placer.cc:874] Const_2: (Const)/</span><span class="string">job:</span>localhost<span class="regexp">/replica:0/</span><span class="string">task:</span><span class="number">0</span>/<span class="string">device:</span><span class="string">GPU:</span><span class="number">1</span></span><br><span class="line"><span class="string">Const_1:</span> (Const): <span class="regexp">/job:localhost/</span><span class="string">replica:</span><span class="number">0</span><span class="regexp">/task:0/</span><span class="string">device:</span><span class="string">GPU:</span><span class="number">0</span></span><br><span class="line"><span class="number">2018</span><span class="number">-03</span><span class="number">-12</span> <span class="number">19</span>:<span class="number">22</span>:<span class="number">00.759167</span>: I tensorflow<span class="regexp">/core/</span>common_runtime<span class="regexp">/placer.cc:874] Const_1: (Const)/</span><span class="string">job:</span>localhost<span class="regexp">/replica:0/</span><span class="string">task:</span><span class="number">0</span>/<span class="string">device:</span><span class="string">GPU:</span><span class="number">0</span></span><br><span class="line"><span class="string">Const:</span> (Const): <span class="regexp">/job:localhost/</span><span class="string">replica:</span><span class="number">0</span><span class="regexp">/task:0/</span><span class="string">device:</span><span class="string">GPU:</span><span class="number">0</span></span><br><span class="line"><span class="number">2018</span><span class="number">-03</span><span class="number">-12</span> <span class="number">19</span>:<span class="number">22</span>:<span class="number">00.759185</span>: I tensorflow<span class="regexp">/core/</span>common_runtime<span class="regexp">/placer.cc:874] Const: (Const)/</span><span class="string">job:</span>localhost<span class="regexp">/replica:0/</span><span class="string">task:</span><span class="number">0</span>/<span class="string">device:</span><span class="string">GPU:</span><span class="number">0</span></span><br><span class="line">[[ <span class="number">44.</span> <span class="number">56.</span>]</span><br><span class="line"> [ <span class="number">98.</span> <span class="number">128.</span>]]</span><br></pre></td></tr></table></figure></p>
<p>从输出结果看,确实使用了两块GPU,基本说明可以同时使用两块GPU。</p>
<h2 id="进一步的例子:CIFAR10多GPU训练"><a href="#进一步的例子:CIFAR10多GPU训练" class="headerlink" title="进一步的例子:CIFAR10多GPU训练"></a>进一步的例子:CIFAR10多GPU训练</h2><p>进一步地,我们采用Tensorflow的tutorial中的一个例子来验证多块GPU卡带来的加速效果。</p>
<p>Tutorial地址见<a href="https://www.tensorflow.org/tutorials/deep_cnn" target="_blank" rel="external">这里</a>。<br>具体model训练程序在<a href="https://github.com/tensorflow/models/tree/master/tutorials/image/cifar10" target="_blank" rel="external">这里</a>。</p>
<p>运行cifar10多GPU训练,<br><figure class="highlight stylus"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">python cifar10_multi_gpu_train<span class="selector-class">.py</span> --num_gpus=<span class="number">2</span></span><br></pre></td></tr></table></figure></p>
<p>可以用如下命令设置每隔一秒查看一下GPU状态:<br><figure class="highlight armasm"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="symbol">nvidia</span>-<span class="keyword">smi </span>-l <span class="number">1</span></span><br></pre></td></tr></table></figure></p>
<p>或者使用gpustat工具,更简洁的观察GPU动态状态变化:<br><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">pip <span class="keyword">install</span> gpustat</span><br><span class="line">watch <span class="comment">--color -n1 gpustat -cpu</span></span><br></pre></td></tr></table></figure></p>
<p>可见在两块Tesla P100上,训练吞吐率大约为 34000 image/sec 左右,在单块 GPU 上,训练吞吐率大约最高为 19000 image/sec。从目前结果看来,虽然两块GPU能够大大加速训练,但是毕竟还是无法做到标准的线性的加速。</p>
<h2 id="本地查看远程服务器Tensorboard-Windows-Linux"><a href="#本地查看远程服务器Tensorboard-Windows-Linux" class="headerlink" title="本地查看远程服务器Tensorboard (Windows, Linux)"></a>本地查看远程服务器Tensorboard (Windows, Linux)</h2><p>核心思想是利用SSH的转发/隧道机制。Tensorboard起在远程服务器本地6006端口,我们本地用一个端口去访问比如16006,我们建立一个隧道,将我们对16006端口的访问转发到远程服务器的6006端口即可。</p>
<p>一般本地和远程在一个局域网内,可以如下做:</p>
<ul>
<li>在Lunux下:</li>
</ul>
<figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-tag">ssh</span> <span class="selector-tag">-L</span> 16006<span class="selector-pseudo">:127.0.0.1</span><span class="selector-pseudo">:6006</span> <span class="selector-tag">user</span>@<span class="keyword">server.address</span></span><br><span class="line">tensorboard –logdir=”tensorboard”</span><br><span class="line">在本地主机访问 http://<span class="number">127.0</span>.<span class="number">0.1</span>:<span class="number">16006</span>/</span><br></pre></td></tr></table></figure>
<p>建立本地16006端口到服务器6006端口的正向转发</p>
<ul>
<li>Windows下:</li>
</ul>
<p>Windows 10中除了一个开发者模式,内嵌一个linux系统,可以进入如上做。但是一般我们在Windows下还是用putty,Xshell,MobaXterm等远程登录软件为主,这里以Xshell为例。</p>
<p>步骤为:<br>1、新建一个会话指向服务器,设置属性,点“隧道”,然后点中间的“添加”,添加的信息如下:</p>
<p><img src="https://blog-image-1256228880.cos.ap-beijing.myqcloud.com/xshell.jpg" alt=""></p>
<p>X11转移那里打钩,也必须保证远程服务器允许X11转发,具体的,在<code>/etc/ssh/sshd_config</code>中设置<code>X11forwarding</code>为<code>yes</code>。</p>
<p>这样即建立了一个隧道。然后在服务器上启动Tensorboard,在本地浏览器打开<code>http://127.0.0.1:16006</code>即可访问TensorBoard。</p>
<ul>
<li>客户端位于外网</li>
</ul>
<p>当然还有一种情况就是客户端位于外网,无法直接建立隧道。</p>
<p>此种情况下 [1],服务器可以通过IP地址寻址客户端,所以在服务器端建立与客户端的反向链接。通过-N -f后台运行。具体命令为:</p>
<p>在服务器主机上执行:<br><figure class="highlight elixir"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ssh -f -NR <client_port><span class="symbol">:localhost</span><span class="symbol">:<server_port></span> [username@]<client_ip_address></span><br></pre></td></tr></table></figure></p>
<p>除了本地查看Tensorboard,也可以启动服务器桌面来直接看Tensorboard。具体可以安装VNC Server和Viewer。</p>
<h2 id="安装VNC-Server-amp-Viewer"><a href="#安装VNC-Server-amp-Viewer" class="headerlink" title="安装VNC (Server & Viewer)"></a>安装VNC (Server & Viewer)</h2><p>1、服务器安装VNC Server: <code>yum -y install tigervnc-server</code></p>
<p>2、配置分辨率和用户登录信息<br><figure class="highlight gradle"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"># vim <span class="regexp">/lib/</span>systemd<span class="regexp">/system/</span>vncserver@.service</span><br><span class="line">写入</span><br><span class="line">VNCSERVERS=<span class="string">"2:root"</span></span><br><span class="line">VNCSERVERARGS[<span class="number">2</span>]=<span class="string">"-geometry 1024x768"</span></span><br></pre></td></tr></table></figure></p>
<p>3、下载VNC Viewer,VNC Viewer连接,可能出现Timeout的问题,可能是服务器设置了防火墙,如下命令关闭:<br><figure class="highlight pf"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">iptables -I INPUT -p tcp --dport <span class="number">5801</span> -j ACCEPT <span class="comment"># 浏览器</span></span><br><span class="line">iptables -I INPUT -p tcp --dport <span class="number">5901</span> -j ACCEPT <span class="comment"># VNC Viewer</span></span><br><span class="line">或者进入/etc/sysconfig/iptables添加一行:</span><br><span class="line">-A INPUT -m <span class="keyword">state</span> --state NEW -m tcp -p tcp --dport <span class="number">5900</span>:<span class="number">5903</span> -j ACCEPT</span><br></pre></td></tr></table></figure></p>
<p>黑屏解决方案:<br>在<code>/root/.vnc/xtartup</code>文件中:<br><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># unset SESSION_MANAGER </span></span><br><span class="line"><span class="comment"># exec /etc/X11/xinit/xinitrc</span></span><br><span class="line">注释掉以上两行,添加如下几行:</span><br><span class="line">[ -x /etc/.vnc/xstartup ] && <span class="built_in">exec</span> /etc/.vnc/xstartup</span><br><span class="line">[ -r <span class="variable">$HOME</span>/.Xresources ] && xrdb <span class="variable">$HOME</span>/.Xresources </span><br><span class="line">xsetroot -solid grey <span class="comment">#vncconfig -iconic & </span></span><br><span class="line">xterm -geometry 80x24+10+10 -ls -title <span class="string">"<span class="variable">$VNCDESKTOP</span> Desktop"</span> &</span><br><span class="line">twm & </span><br><span class="line">gnome-session &</span><br></pre></td></tr></table></figure></p>
<h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>本文主要讲述了CUDA 8.0的安装细节,TensorFlow的安装,多GPU训练实例,以及远程服务器TensorBoard查看,以及VNC(Virtual Network Console)的服务端和客户端的安装。<br>更多的一些错误及解决方案因为目前还解决不全,一律放到后面的<a href="">《TensorFlow, GPU错误及优化集锦》</a></p>
<h2 id="References"><a href="#References" class="headerlink" title="References"></a>References</h2><p><a href="http://blog.csdn.net/silent56_th/article/details/69367446" target="_blank" rel="external">[1] 远程使用内网服务器的tensorboard和jupyter notebook</a></p>
<p><a href="http://www.cnblogs.com/-chaos/p/3378564.html" target="_blank" rel="external">[2] ssh -D -L -R 差异</a></p>
<p><a href="https://zhuanlan.zhihu.com/p/31457591" target="_blank" rel="external">[3] 跑深度学习代码在linux服务器上的常用操作(ssh,screen,tensorboard,jupyter notebook)</a></p>
<p><a href="http://www.xshellcn.com/wenti/xsh-ssh.html" target="_blank" rel="external">[4] 如何在xshell中创建一个SSH隧道</a></p>
<p><a href="https://zhuanlan.zhihu.com/p/31558973" target="_blank" rel="external">[5] 科普帖:深度学习中GPU和显存分析</a></p>
<p>事前各软件版本:<br>NVIDIA驱动:390.25<br>CUDA: 9.1</p>
<p>现在Tensorflow不支持CUDA 9.1,所以采用降级的办法来解决,将CUDA降为8.0,由于NVIDIA驱动可以向下兼容,所以不用卸载NVIDIA驱动。当然也可以不卸载9