进程、轻量级进程(LWP)、线程

  • 进程:程序执行体,有生命期,用来分配资源的实体
  • 线程:分配CPU的实体。
    • 用户空间实现,一个线程阻塞,所有都阻塞。
    • 内核实现,不会所用相关线程都阻塞。用LWP实现,用线程组表示这些线程逻辑上所属的进程。

进程描述符

  • 进程描述符(简称pd, process descriptors),结构体是:task_struct

    • 数据较多,存放在kenerl的动态内存空间。
    • pd的引用放在thread_info中,
      • thread_info与内核栈,放在一个8K空间(它的地址8K对齐)。内核程序使用的栈空间很小。
      • thread_info在底部,内核栈在顶部向下增长。
        • 好处:多CPU时方便,每个CPU根据自己的栈指针就可以找到当前的pd (以后用current表示当前CPU运行的进程描述符)。

          • esp(内核栈指针)低8位置零,就是thread_info地址。
      • 每进程有自己的thread_info, (分配释放函数: alloc_thread_info, free_thread_info)
  • 描述符的内容

    • 相关的ID (一个4元素数组)

      • 进程ID (PID)

        • PID按创建顺序连续增长,到最大值后从最小值开始。
        • 0号进程:交换进程(swapper)
        • 有PID可用位图,表示那一个PID可用,至少占一个页。
      • 线程组ID(tgid),用LWP实现多线程支持
        • 多进程时,进程id,就是线程组id, 也就是组长的pid(LWP)。 getpid() 取的是线程组的id(tgid), 也是组长的pid.
        • 单线程时,pid = gid。所以getpid,也是真正的pid.
      • 进程组ID(pgrp)。
      • 回话的ID(session).
        • 组ID,都是组长的PID。FIXME: 但pb也有各组长的PID

          • 线程组长:tgid
          • 进程组长:signal->pgrp ,
          • 会话长:signal->session
      • 管理ID数据结构——哈希表管理 (利用id找到所用相关的pd,方便)。
        • 一个哈希表数组(pid_hash),存放四个哈希表, 每一个表代表一类id (pid, tgid, pgrp, session)
        • 每个哈希表的由数组(索引为哈希值)和二维链表(嵌入到进程描述符内的pids中)实现
          • 第一维链表:哈希冲突链表。
          • 第二维链表:要查找的值相同的链表, 叫per-PID list(同一组的所有线程,同一组的所有进程,同一会话的所有进程);
      • 进程组ID(pgrp), 回话ID(session)在共享信号的数据结构里。因为同一进程内的所有LWP,这两个ID都是一样的
    • 家族关系:由pd里的链表(下级)和pd指针(上级)实现

      • 关系:

        • 亲生父亲:创建自己的进程,或是托孤进程(创建自己的进程死了)。
        • 父亲:自己死时要发信号告知的。一般是亲生父亲,有时是监控自己的进程 (调用ptrace)
        • 孩子:
        • 兄弟:
      • 监控(自己起的名字,类似于监护。由于管理方式相同,也归为家族关系)
        • 监控的进程列表:ptrace_children
        • 被监控的其他进程:ptrace_list (类似于被监控的兄弟)
      • 在链表里为了管理方便:
        • 最大儿子的兄弟是父亲
        • 最小儿子的弟弟也是父亲
        • 父亲保管最大儿子,和最小儿子
    • 进程资源及资源限制:

      • CPU相关:

        • 占用CPU总时间
        • 用户的最大进程数
      • 内存相关:
        • 进程地址空间
        • 锁住内存大小
        • 进程页数 (只有记录,没有限制)
        • 堆大小,栈大小
      • 资源相关:
        • 文件:

          • core dump大小
          • 最大文件大小
          • 打开文件个数
        • 进程同步与通信
          • 锁数目,
          • 悬挂信号数据
          • 在消息列队中占的大小
      • 相关数据结构 和 处理流程
        • pd->sigal->rlim 是一个表示进程资源使用情况以及限制的结构 的数组。
        • 表示进程资源使用情况以及限制的结构:包含当前值,最大值两个数值。
      • 只有超级用户才能增大资源限制。
      • 一般用户登陆时:
        • kernel创建root进程,减少limit,
        • 建一个 shell子进程,继承limit.
        • 把shell进程的用户,改成登陆的那个用户
    • 进程状态(state)

      • 运行,TASK_RUNNING

        • 组织pd的结构:就绪进程链:

          • 一个CPU一组链表,每个链表表示一种优先级。
      • 阻塞
        • 可中断阻塞,TASK_INTERRUPTIBLE

          • 可被硬件中断,“释放资源”事件,信号唤醒。
        • 不可中断阻塞,TASK_UNINTERRUPTIBLE
          • 可被硬件中断,“释放资源”事件,唤醒。
          • 但不能被信号唤醒。可用于驱动程序中。
        • 组织pb的结构:等待列队: 每一类事件一个列队,用内嵌链表实现(虽然没列出内嵌链表节点)
          • 列队头:

            • 自旋锁:防止有一个主函数和中断函数同时操作列队。
          • 列队节点:
            • 独占标志:表示该进程是否要独占资源 (不再唤醒别的进程)
            • 指向pd的指针
            • 用于唤醒进程的回调函数。(提供进程的执行机会,是否操作等待列队由用户决定)
      • 停止
        • 停止TASK_STOPPED

          • 被信号停止
        • 追踪TASK_TRACED
          • 该进程被一个调试进程监控以后,收到任何一个信号就进入该状态
        • 组织pb的结构:FIXME: 信号的等待列队?
      • 退出
        • 退出_僵尸EXIT_ZOMBIE

          • 进程终止,资源没有被回收(父进程要用,没有调wait系列函数)
        • 退出_死亡EXIT_DEAD
          • 进程终止,资源正在被回收(父进程要用,没有调wait系列函数)。
          • 一旦资源回收完成,进程描述符也就被回收了。
          • 它防止该进程再次被wait.
        • 组织pb的结构:不挂到队列上,只在家族关系中,等待父进程收回资源

进 程控制 :

  • 阻塞(current阻塞到某个列队上):

    • 基本流程

      • 临时生成一个列队节点,初始化。
      • 改变current的状态,放入节点,挂到列队上。
      • 调度 (=====》至此,阻塞完成。 一旦被别的进程唤醒====》从调度函数中返回)
      • 从等待列队上摘除节点。
    • 变化:
      • 将挂列队、调度、从列队删除三步拆开,便于灵活处理。
      • 可中断的、限时、独占的函数类似。只不过进程状态、调度函数、独占标志不同。
      • 非独占的从列队开始添加,独占的从末尾添加。(但一个列队内既有独占的,又有非独占的等待进程,很少见)
  • 唤醒:

    • 基本流程

      • 唤醒一个进程:调用节点里的回调函数
      • 唤醒的时候从列队开头依次唤醒,直到唤醒一个独占的后停止。
    • 变化
      • 是否只唤醒可中断的进程. (_interruptible后缀)
      • 唤醒的独占进程的数目(1个,多个(_nr后缀),所有(_all后缀))
      • 唤醒后是否不检查优先级,马上给予CPU (有_sync的不检查优先级)。
  • 进程切换

    • 切换pgd (全局页目录),此章不讨论。
    • 切换内核栈,硬件上下文
      • 硬件上下文,就是CPU的寄存器。

        • 一部分(大多数CPU寄存器(除了通用寄存器))在pd中保存(task_struct->thread, 类型是thread_struct),
        • 一部分(通用寄存器)保存在内核栈中.
      • 原来用硬件指令()保存CPU信息。后来改成软件(一个个MOV指令)
        • 容易控制,可以挑选信息保存,便于优化。不保存的做其他用(如:进程间传递)

          • far jmp:跳至目标进程的TSSD。而linux是每个CPU一个TSS,不是每进程一个
        • 对于一些寄存器(ds、es)可以检查值。
        • 与用硬件指令保存时间差不多。
    • switch_to 宏
      • 三个参数:

        • prev: 要换走的进程,一般是当前进程
        • next: 要换到的进程。
        • last: 传出参数。当前进程再次被换到时,最后一个占用CPU的进程。(prev指向的进程 就是 next指向的进程 的last)
      • 步骤:
        • 栈切换, 完成后就是在新进程的上执行了:

          • 保存prev(放在eax)
          • eflags,ebp入内核栈;
          • 保存并装载新的esp (旧的esp放到prev->thread.esp,新的esp是next->thread.esp)
            • 此时current就是新的esp所指的thread_info内的task指针
        • 设置返回地址:
          • prev进程以后得到执行时的__switch_to的返回地址: __switch_to后的第一条指令, 放入prev->thread.eip,
          • 准备next进程的从__switch_to返回的地址: next->thread.eip入栈.
        • 调用__switch_to ()函数,该函数动作如下:
          • 更新CPU的相关信息(tss和gdt):

            • 存next->thread.esp0(内核栈低)到本地TSS.esp0中。
            • 所在CPU的全局段表里的TLS段, 设成next进程的.
            • 更新tss的I/O位图.
          • 更新CPU的寄存器(pd->thread (tss) 与 CPU寄存器交换数据):
            • 保存FPU, MMX, XMM寄存器, 先不装载以后需要时通过中断装载(TODO: )
            • 保存prev的fs, gs寄存器. 装载next的
            • 装载next的debug寄存器(debug寄存器一个8个, 进程切换时只需6个)
          • 返回
            • prev放入eax (prev就是新进程的last)
            • ret
        • ret返回的地址: (__switch_to之前被存入栈中, __switch_to ret时进入eip)
          • 如果是next新进程, next->thread.eip是iret_from_fork.
          • 如果next不是新进程:
            • 弹出ebp, elfags
            • 把eax放入last变量 (prev就是next进程的last)

任务状态段(一个存CPU状态的数组,tss_struct init_tss[])

  • 每个CPU用段上的一个元素。(FIXME: 用于:用户模式要进入内核模式时,设置相应寄存器)

    • TSS上存内核栈地址。CPU上的程序从用户模式转到内核模式,设置esp。
    • TSS存I/O端口许可位图。用户模式程序用到I/O时,检查有无权限
    • 所以,进程切换时,要保存的寄存器在pd->thread中。
      • thread_struct不是thread_info。thread_info中只有少量的数据或指针, 用于通过esp快速定位数据
  • 进程切换时,更新TSS上的信息。
    • CPU控制单元再从TSS上取需要的信息。
    • 即反应了CPU的当前进程情况,又不需要维护所有进程的状态数据。
  • TSS的描述符在GDT里。
    • TSSD:任务状态段描述符 (其实应该叫任务状态描述符,每个TSSD,表示一个CPU的状态, FIXME: :具体以源码为准)
    • CPU原始设计,每个进程一个TSS元素。
    • linux设计,每个CPU一个TSS元素。
    • cpu里的tr寄存器,保存着自己的TSSD(即init_ttss[cpu_id]),不用总上gdt里去取。
    • 进程创建: clone, fork, vfork系统调用

    • clone系统调用
      • 参数:

        • 执行函数(fn), 参数(arg)
        • flags|死亡时给父进程发的信号 (clone_flags): 以下介绍clone_flags
          • 资源共享

            • 段,页,打开文件共享:

              • 页表(不是页, CLONE_VM),
              • 打开文件(clone_files),
              • 建一个新tls段(clone_settls)
            • 路径和权限设置:
              • clone_fs: 共享根目录, 当前目录, 创建文件初始权限.
              • clone_newns: 新的根路径, 自己的视野看文件系统
            • 线程通信
              • clone_sighand: 信号处理action, 阻塞和悬挂的信号
              • clone_sysvsem: 共享undoable信号量操作
          • 进程关系
            • 同父: clone_parent 创建进程与新进程是兄弟 (同父), 新进程不是创建进程的子进程

              • 为了方便期间, 以下讨论暂时不考虑这一因素(它很容易实现), 认为创建进程就是父进程
            • 同一个线程组: clone_thread. 属于同一个进程(线程组)
            • 都被trace: clone_ptrace
            • 子进程不被trace: clone_untrace (内核设置, 覆盖clone_ptrace)
          • 返回tid
            • 向父进程返回tid: clone_parent_settid
            • 向子进程返回tid: clone_child_settid
          • 子进程的状态:
            • 子进程开始就stop: clone_stopped
          • 进程死亡或exec通知:
            • 启动内核机制: 如果子进程死亡或exec, 它自己空间内的tid(*ctid)清零, 并唤醒等待子进程死亡的进程.
        • 赋给子进程的资源
          • 子进程的栈(父进程alloc的内存地址)
          • 线程局部仓库段(tls)
        • 返回子进程tid的地址
          • 父进程用户空间内的地址
          • 子进程用户空间的地址
  • clone, fork, vfork实现方式
    • 大致相同:

      • 系统调用服务例程sys_clone, sys_fork, sys_vfork三者最终都是调用do_fork函数完成.

        • do_fork的参数与clone系统调用的参数类似, 不过多了一个regs(内核栈保存的用户模式寄存器). 实际上其他的参数也都是用regs取的
    • 区别在于:
      • clone:

        • clone的API外衣, 把fn, arg压入用户栈中, 然后引发系统调用. 返回用户模式后下一条指令就是fn.
        • sysclone: parent_tidptr, child_tidptr都传到了 do_fork的参数中
        • sysclone: 检查是否有新的栈, 如果没有就用父进程的栈 (开始地址就是regs.esp)
      • fork, vfork:
        • 服务例程就是直接调用do_fork, 不过参数稍加修改
        • clone_flags:
          • sys_fork: SIGCHLD|0;
          • sys_vfork: SIGCHLD| (clone_vfork | clone_vm)
        • 用户栈: 都是父进程的栈.
        • parent_tidptr, child_ctidptr都是NULL.
  • 具体实现函数do_fork() (内核函数)的工作流程:

    • 分配PID, 确定子进程到底是否traced.

      • 分配空闲的PID
      • 确定clone_ptrace位. (确定子进程到底要不要被trace, 而不是参数所说的希望被trace)
        • 设置该位: 参数已设该位, 且创建线程被trace中
        • 清除该位: 父进程没有被trace, 或 clone_untrace已经设置.
    • 复制进程描述符(copy_process)
      • 检查clone_flags是否兼容, 是否安全

        • clone_newns 与 clone_fs 互斥
        • clone_sighand 是 clone_thread 的必要条件: 线程必须共享信号处理
        • clone_vm 是 clone_sighand 的必要条件 : 共享信号处理, 首先要共享信号处理的代码(在进程页面里)
        • 附加的安全检查: security_task_create(clone_flags)
      • 复制进程描述符
        • 在父进程的thread_info里保存浮点寄存器: __unlazy_fpu()
        • 分配新的进程pd(alloc_task_struct), 并拷贝父进程pd
        • 分配新的thread_info(alloc_thread_info), 并拷贝父进程的thread_info.
        • 新的thread_info和新分配的pd 相互引, 新pd的引用计数设为2 (表示:新pd有用, 且不是僵尸进程)
      • 相关计数加1: (此处先相关计数检查, 都通过后再都加1)
        • 检查并增加: 用户拥有进程数, 系统总共进程数.

          • 一般来说, 所有进程的thread_info总和, 不超过物理内存的1/8
        • 新进程的可执行格式的引用计数(FIXME: pd里标有可执行个数吗)
        • 系统执行fork总数.
      • 进程pd的关键域的设置(顺序与源码可能不一致):
        • 进程关系

          • 设置父子关系 (parent, real_parent, 考虑被trace的情况)
          • 设置新pd的PID
          • 设置tgid, 线程组长的pd(pd->group_leader). (根据是不是线程组长, 即clone_thread位是否为0)
          • 加入PID哈希表(pid, tgid, 如果是进程组长加入pgid和sid表),(调attach_pid())
          • 拷贝tid到父进程的用户空间(parent_tidptr)
        • 拷贝资源(如果clone_flags没标明共享):
          • 文件,目录,内存:copy_files, copy_mm, copy_namespace,
          • 进程通信: copy_signal, copy_sighand, copy_semundo
        • 设置子进程的内核栈(thread_info), 内核态相关寄存器(thread_struct, 不知道这个结构的具体用处): copy_thread()
          • 子进程的thread_struct:

            • esp, esp0 - 内核栈顶, 内核栈底
            • eip - ret_from_fork()的地址 (用户态切到内核态的第一条指令)
            • I/O许可位图 - 如果父进程有, 就拷贝一份过来
            • TLS - 如果用户空间提供了TLS段, 拷贝过来
          • 设置子进程的内核栈:
            • child_regs.esp = 传入的栈地址参数;
            • child_regs.eax = 0, 给用户态的返回值是0
            • 清除thread_info中的, TIF_SYSCALL_TRACE位, 防止运行ret_from_fork时, 系统通知调试进程
            • 设置子进程的thread_info的cpuid
        • 设置调度信息(sched_fork())
          • 设置task_running状态,
          • 初始化调度参数(时间片),
          • 子进程禁止内核抢占(thread_info.preempt_cout = 1)
        • 其他:
          • 如果没有被trace,pd->ptrace = 0;
          • 设置pd->exit_signal:
            • 有clone_thread位: 设为参数clone_flags中的退出信号
            • 没有clone_thread位: 设为-1 (表示进程终止时, 该LWP不给父进程发信号)
          • pd->flags: 清除PF_SUPERPRIV , 设置PF_FORKNOEXEC
          • 大内核锁 pd->lock_depth = -1
          • exec次数: pd->did_exec = 0
          • 拷贝child_tidptr到pd->set_child_tid. 以备子进程开始执行时, 把tid放到自己内存空间的child_tidptr
      • 返回pd
    • 设置父子进程的运行状态, 调度信息
      • 设置子进程的状态.

        • 挂信号: 如果创建出来的是停止(clone_stopped)或被trace(pd->ptrace里有PT_PTRACE位)的进程, 悬挂一个SIGSTOP信号.

          • 只有debugger发出SIGCONT信号后, 才能进入运行状态
        • 设状态,入列队:如果有clone_stopped位, 子进程设为stopped状态; 否则调用wake_up_new_task(), 把子进程加入就绪列队:
          • 调整父进程和子进程的调度参数 (主要是时间片)
          • 如果父子在同一CPU上运行, 且页表不同享, 子进程在插在父进程前
            • 子进程很可能exec, 不与父进程共享页. 这样防止父进程无用的copy on write.
          • 如果不同CPU上运行, 或者共享页表, 子进程放在列队最后
      • 如果父进程处于被调试状态, 程通知调试器
        • 当前进程给debugger进程发信号, 告知自己创建了子进程; 并停止自己(进入traced状态), 使debugger运行.

          • 子进程的pid保存在current->ptrace_message中, 供debugger用
          • 调试器发信号, 使父进程继续后, 再进行下一步; 否则父进程一直处于traced状态
      • 设置父进程状态
        • 如果有clone_vfork, 把自己放到一个等待列队.

          • 内核处理完系统调用后, 会执行调度, 这样就阻塞父进程了.
          • 直到子进程释放了它的内存地址空间, 即子进程终止或exec新程序, 用信号唤醒父进程.
    • 返回子进程的pid.
    • 子进程被调度后,执行pd.thread.eip(ret_from_fork). 调用关系(=>): ret_from_fork=>schedule_tail=>finish_task_switch.
      • schedule_tail的另一件事就是: 把pid保存到地址pd->set_child_tid (创建进程使的parent_tidptr)
      • finish_task_switch的动作是: 装载内核栈保存的寄存器(regs->eax为0),返回到用户态。系统调用返回值就是eax(0)
  • 内核线程:

    • 只运行于kernel模式,只能访问大于3G的空间。而普通进程在内核模式时,能访问整个4G空间
    • 创建方法, 类似于clone
      • 准备返回地址fn: 构造一个regs. 里面有fn, args, __KERNEL_CS等. regs->eip是汇编函数kernel_thread_helper
      • do_fork (flags|CLONE_VM|clone_untraced, 0, &regs, 0, NULL, NULL)
        • 创建线程, 与父进程共享页. 用上步构造的regs初始化新程的内核栈
      • 新线程被调度后. 由ret_from_fork, 用regs恢复寄存器, 开始执行kernel_thread_helper
      • kernel_thread_helper: 把args压入栈, call fn(args, fn都寄存器中)
    • 典型的内核线程:
      • 进程0: 所有进程的祖先

        • 编译时存在.

          • pd, 内核栈: init_task, init_thread_union
          • 资源: init_mm, init_files, init_fs.  信号: init_signals, init_sighand
          • 页表: swapper_gd_dir
        • 功能
          • 初始化系统数据,

            • 多CPU系统中, 开始时BIOS禁用其他CPU.
            • 初始化系统数据后, 进程0拷贝自己到其他CPU的调度列队上, 启动其他CPU, 所有的PID都是0.
          • 使能中断
          • 创建内核线程1, (函数是init)
          • 进入idle
      • 进程1:
        • init函数 exec可执行文件init, 使内核线程变成了普通进程.
        • 管理其他进程, 称为托孤进程
      • 其他内核线程:
        • 执行工作列队:

          • ksoftirqd: 执行 softlets
          • kblockd: 执行工作列队 kblockd_workqueue, 定期激活块设备驱动
          • keventd (又叫events): 处理工作列队 keventd_wq
        • 管理资源:
          • kapmd: 电源管理
          • kswapd: 交换进程, 用于回收内存资源
          • pdflush: flush脏的磁盘缓存

进程销毁

  • 进程终止

    • 系统调用

      • 整个进程终止: exit_group(), 由do_group_exit处理系统调用. c函数 exit()也是用的这系统调用
      • 某个线程终止: _exit(), 由do_exit处理. C函数中用到此系统调用的API: pthread_exit
    • do_group_exit流程: (整个组内至少有一个线程调用它, 用于整组协调)
      • 检查线程组的退出过程是否启动: 检查signal_group_exit(线程组内的公共数据)是否非零. 如果没有启动, 执行一下操作来启动退出过程:

        • 设置启动标志signal_group_exit.
        • 存储终止码(exit_group的参数), 在current->signal->group_exit_cold
        • 向其他线程发SIG_KILL信号, (它们收到信号后, 调do_exit())
      • 调用do_exit, 使本线程退出
    • do_exit流程:
      • 设置线程的终止标志, 退出码

        • 设置PF_EXITING, 标明要被终止
        • 设置pd->exit_code
          • 系统调用参数
          • 或是内核提供的错误码, 表示异常终止
      • 释放资源:
        • 删除该进程的定时器
        • 去除对资源的引用:
          • exit_mm, __exit_files;
          • __exit_fs(root路径,工作路径, 创建文件权限), exit_namespace(挂载的文件系统的视野);
          • exit_thread(thread_struct), exit_sem,
      • 如果这个线程的函数实现了一种可执行格式, 可执行格式数的引用计数--; FIXME: 还没看到这块儿, 凑合翻译的不一定对
      • 改变父子关系, 并向父进程发信号, 改变自己的状态(exit_notify)
        • 托付终止线程创建的子进程:

          • 如果终止线程还有同组线程: 终止线程创建的子进程, 作为与同组线程的子进程.
          • 否则: 终止线程创建的子进程, 作为孤儿进程, 由init进程托管
        • 向父进程发信号
          • exit_signal有意义 && 最后线程 :  发exit_signal
          • 否则:
            • 被trace : 发SIGCHLD
            • 没被trace : 不发信号
        • 僵尸自己或直接死亡,  并设置PF_DEAD位
          • exit_signal没意义 && 没被trace : 直接死亡 (这种情况没有发信号)

            • 变成EXIT_DEAD状态,
            • release_task() (后面介绍). pd引用计数变为1, 不会马上释放
          • 否则: 僵尸
            • exit_signal有意义 || 被trace : 僵尸
          • 整理"僵尸"与"发临僵尸信号"的关系:
            • 将发信号的条件中"最后线程"去掉, 可简化为(exit_signal有意义)||(被trace) == (发信号)
            • 可得出后: (发信号) == (僵尸)
            • 又可推出: (没有trace && exit_signal有意义 && 不是最后进程) == (僵尸了,但没法信号) , 这种情况在移除死进程时, 会给其父进程发信号 (FIXME: 待验证)
      • 调度. 调度函数会忽略僵尸进程, 但会减少僵尸进程的pd的使用计数; 会检查PF_DEAD位, 把它变成exit_dead状态

进程移除 TODO:

进程、轻量级进程(LWP)、线程的更多相关文章

  1. Linux线程 之 线程 线程组 进程 轻量级进程(LWP)

    Thread Local Storage,线程本地存储,大神Ulrich Drepper有篇PDF文档是讲TLS的,我曾经努力过三次尝试搞清楚TLS的原理,均没有彻底搞清楚.这一次是第三次,我沉浸gl ...

  2. Linux线程 之 线程 线程组 进程 轻量级进程(LWP) -systemtap -mysql

    http://blog.chinaunix.net/uid-24774106-id-3650136.html http://blog.itpub.net/15480802/viewspace-7627 ...

  3. Linux下的进程类别(内核线程、轻量级进程和用户进程)--Linux进程的管理与调度(四)

    本文中出现的,内核线程,轻量级进程,用户进程,用户线程等概念,如果不太熟悉, 可以参见 内核线程.轻量级进程.用户线程三种线程概念解惑(线程≠轻量级进程) Linux进程类别 虽然我们在区分Linux ...

  4. 进程、线程、轻量级进程、协程与 go 的 goroutine【转载+整理】

    本文内容 进程 线程 协程 Go 中的 goroutine 参考资料 最近,看一些文章,提到"协程"的概念,心想,进程,线程,协程,前两个很容易,任何一本关于操作系统的书都有说,开 ...

  5. 进程、线程、轻量级进程、协程和go中的Goroutine

    进程.线程.轻量级进程.协程和go中的Goroutine 那些事儿电话面试被问到go的协程,曾经的军伟也问到过我协程.虽然用python时候在Eurasia和eventlet里了解过协程,但自己对协程 ...

  6. linux内核——进程,轻量级进程,线程,线程组

    1.进程.轻量级进程.线程.线程组之间的关系 2.及它们的标识相关说明 一.进程.轻量级进程.线程.线程组之间的关系 借助上图说明: 进程P0有四条执行流,即线程, 主线程t0是它的第一个线程,且与进 ...

  7. Go语言 进程、线程、轻量级进程、协程和go中的Goroutine 那些事儿

    原文:http://www.cnblogs.com/shenguanpu/archive/2013/05/05/3060616.html 电话面试被问到go的协程,曾经的军伟也问到过我协程.虽然用py ...

  8. 进程、线程、轻量级进程、协程与 go 的 goroutine

    本文内容 进程 线程 协程 Go 中的 goroutine 参考资料 最近,看一些文章,提到“协程”的概念,心想,进程,线程,协程,前两个很容易,任何一本关于操作系统的书都有说,开发时也经常用,但是协 ...

  9. python基础-12 多线程queue 线程交互event 线程锁 自定义线程池 进程 进程锁 进程池 进程交互数据资源共享

    Python中的进程与线程 学习知识,我们不但要知其然,还是知其所以然.你做到了你就比别人NB. 我们先了解一下什么是进程和线程. 进程与线程的历史 我们都知道计算机是由硬件和软件组成的.硬件中的CP ...

随机推荐

  1. [NOIP2001] 提高组 洛谷P1026 统计单词个数

    题目描述 给出一个长度不超过200的由小写英文字母组成的字母串(约定;该字串以每行20个字母的方式输入,且保 证每行一定为20个).要求将此字母串分成k份(1<k<=40),且每份中包含的 ...

  2. [NOIP2000] 提高组 洛谷P1023 税收与补贴问题

    题目背景 每样商品的价格越低,其销量就会相应增大.现已知某种商品的成本及其在若干价位上的销量(产品不会低于成本销售),并假设相邻价位间销量的变化是线性的且在价格高于给定的最高价位后,销量以某固定数值递 ...

  3. ES6__字符串、数组、对象的扩展

    /** * 字符串的扩展 */ // 模板字符串 tab上面的反向符号 // 添加${} // let flag = true; // // let html = `<ul> // < ...

  4. svg学习之旅(3)

    常用标签: <g>标签 是一个容器(分组)标签,用来组合元素的 - 共用属性 - transform = "translate(0,0)"<text>标签 ...

  5. PHP 常见问题3

    1,Http 和 Https 的区别 第一:http 是超文本传输协议,信息是明文传输,https 是具有安全性的 ssl 加密传输协议 第二:http 和 https 使用的是完全不同的连接方式,端 ...

  6. JDBC调用存储过程,进参出参

    今天做了一个数据表拷贝的功能,用到了存储过程,就写了一个java中用jdbc调用存储过程的代码,成功的实现了功能,晚上跑回家记录下 Connection conn = ConnectionUtil.g ...

  7. 44444444444444444444444444444444dddddddddd66666666666666666666666666

    dddddddddddddddddddddddddddddddddddddddddddddddddddd

  8. 纯Java Web项目下的Session共享方案收集(待实践)

    1.使用filter方法存储 这种方法比较推荐,因为它的服务器使用范围比较多,不仅限于tomcat ,而且实现的原理比较简单容易控制. 可以使用memcached-session-filter 官方网 ...

  9. @Aspect注解无效

    Pointcut的execution配置正确的话,检查下,是否加了以下jar包 <!-- http://mvnrepository.com/artifact/org.aspectj/aspect ...

  10. 深度学习综述(LeCun、Bengio和Hinton)

    原文摘要:深度学习可以让那些拥有多个处理层的计算模型来学习具有多层次抽象的数据的表示.这些方法在很多方面都带来了显著的改善,包含最先进的语音识别.视觉对象识别.对象检測和很多其他领域,比如药物发现和基 ...