zoukankan      html  css  js  c++  java
  • golang fork and docker fork

    背景

    在日常的系统编程中,我们经常有这样的需求:fork 多个子进程去执行任务。例如典型的 master-slave 架构便是这样的,fork 调用之后,父进程开始作为管理进程管理子进程,而子进程则开始执行处理请求的代码。但是在Go语言中,由于Go语言屏蔽了进程、线程的概念,而只提供了 goroutine 的概念,导致我们无法很好的直接操作 fork 调用。在Go语言中,此类解决方案只有以下三种:

    这三个都类似于 fork + exec,但是没有类似C中的fork调用可以达到在fork之后根据返回的pid然后进入不同的函数的方案。原因主要是:

    • fork 早出现在只有进程,没有线程的年代
    • C中是自行控制线程,这样fork之后才不会发生紊乱。一般都是单线程fork之后,才会开始多线程执行。
    • Go中多线程是runtime自行决定的,所以Go中没有提供单纯的fork,而是fork之后立即就exec执行新的二进制文件

    为了实现上述需求,我们需要利用Go语言的一些特性进行模拟。

    C语言的实现方式

    在了解Go如何实现fork之前,先看一下C里的传统使用方式:

    #include <sys/types.h>
    #include <unistd.h>
    #include <stdio.h>
    #include <sys/wait.h>
    
    void child() {
        printf("child process
    ");
    }
    
    int main() {
        printf("main process
    ");
        pid_t pid = fork();
        int wstatus;
    
        if (pid == 0) {
            child();
        } else {
            printf("main exit
    ");
            waitpid(pid, &wstatus, 0);
        }
    }
    

    运行一下:

    $ gcc main.c && ./a.out 
    main process
    main exit
    child process
    

    Go语言的实现方案

    然后我们看看Docker提供的实现fork的方式:

    package main
    
    import (
    	"log"
    	"os"
    
    	"github.com/docker/docker/pkg/reexec"
    )
    
    func init() {
    	log.Printf("init start, os.Args = %+v
    ", os.Args)
    	reexec.Register("childProcess", childProcess)
    	if reexec.Init() {
    		os.Exit(0)
    	}
    }
    
    func childProcess() {
    	log.Println("childProcess")
    }
    
    func main() {
    	log.Printf("main start, os.Args = %+v
    ", os.Args)
    	cmd := reexec.Command("childProcess")
    	cmd.Stdin = os.Stdin
    	cmd.Stdout = os.Stdout
    	cmd.Stderr = os.Stderr
    	if err := cmd.Start(); err != nil {
    		log.Panicf("failed to run command: %s", err)
    	}
    	if err := cmd.Wait(); err != nil {
    		log.Panicf("failed to wait command: %s", err)
    	}
    	log.Println("main exit")
    }
    

    运行一下:

    $ go run main.go
    2018/03/08 19:52:39 init start, os.Args = [/tmp/go-build209640177/b001/exe/main]
    2018/03/08 19:52:39 main start, os.Args = [/tmp/go-build209640177/b001/exe/main]
    2018/03/08 19:52:39 init start, os.Args = [childProcess]         -----------第二次调用
    2018/03/08 19:52:39 childProcess
    2018/03/08 19:52:39 main exit
    

    这样我们就使用Go语言模拟了传统的 fork 调用。

    其原理是在Go语言中,运行时会保证 init 一定会在 main 之前执行。而初次执行程序的时候 os.Args[0] 是可执行文件的名字,但是 reexec.Command 却可以修改子进程的 os.Args[0],所以子进程会直接找到 reexec.Init 上reexec.Register 所注册的函数,然后执行,返回true,最后调用 os.Exit(0),因此就能达到传统的 fork 之后,子进程执行另外一个函数的效果。

    总结

    通过利用Go语言里,runtime 总是会保证 init 函数优先于 main 函数执行的特点,我们模拟了传统的 fork 调用,使得Go语言也可以方便的像其他语言一样,调用 fork 之后执行代码中的其他代码段。

  • 相关阅读:
    配置eclipse链接hadoop
    hdfs命令总结
    hadoop环境搭建之分布式的建立6
    win10任务视图的时间线历史记录无法删除
    Flask——server.py引入其他python文件
    《架构即未来》阅读笔记3
    msi文件无文件关联,右键打开方式找不到Windows® 安装程序
    bat文件无文件关联,双击无法启动,但可拖进dos窗口运行
    软件无法安装,每次打开安装包都是显示释放安装文件,然后就无反应了
    开始菜单右键打不开,win+x键无反应
  • 原文地址:https://www.cnblogs.com/dream397/p/13998970.html
Copyright © 2011-2022 走看看