自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

探索Go守護(hù)進(jìn)程的實(shí)現(xiàn)方法

開(kāi)發(fā) 前端
在Go中實(shí)現(xiàn)守護(hù)進(jìn)程化,雖然因?yàn)檎Z(yǔ)言運(yùn)行時(shí)的特性而具有挑戰(zhàn)性,但通過(guò)社區(qū)開(kāi)發(fā)的庫(kù)和謹(jǐn)慎的實(shí)現(xiàn)是可以實(shí)現(xiàn)的。

在后端開(kāi)發(fā)的世界里,守護(hù)進(jìn)程(daemon)這個(gè)概念與Unix系統(tǒng)一樣古老。守護(hù)進(jìn)程是在后臺(tái)運(yùn)行的長(zhǎng)期服務(wù)程序,不與任何終端關(guān)聯(lián)。盡管現(xiàn)代進(jìn)程管理工具如systemd[1]和supervisor[2]等讓?xiě)?yīng)用轉(zhuǎn)化為守護(hù)進(jìn)程變得十分簡(jiǎn)單,我們甚至可以使用以下命令來(lái)在后臺(tái)運(yùn)行程序:

nohup ./your_go_program &

但在某些情況下,程序的原生轉(zhuǎn)化為守護(hù)進(jìn)程的能力仍然是有必要的。比如分布式文件系統(tǒng)juicefs cli的mount子命令,它就支持以-d選項(xiàng)啟動(dòng),并以守護(hù)進(jìn)程方式運(yùn)行:

$juicefs mount -h
NAME:
   juicefs mount - Mount a volume

USAGE:
   juicefs mount [command options] META-URL MOUNTPOINT

... ...

OPTIONS:
   -d, --background  run in background (default: false)
   ... ...
... ...

這種自我守護(hù)化的能力會(huì)讓很多Go程序受益,在這一篇文章中,我們就來(lái)探索一下Go應(yīng)用轉(zhuǎn)化為守護(hù)進(jìn)程的實(shí)現(xiàn)方法。

1. 標(biāo)準(zhǔn)的守護(hù)進(jìn)程轉(zhuǎn)化方法

[W.Richard Stevens]( "W.Richard Stevens")的經(jīng)典著作《UNIX環(huán)境高級(jí)編程[3]》中對(duì)將程序轉(zhuǎn)化為一個(gè)守護(hù)進(jìn)程的 (daemonize) 步驟進(jìn)行了詳細(xì)的說(shuō)明,主要步驟如下:

  • 創(chuàng)建子進(jìn)程并終止父進(jìn)程

通過(guò)fork()系統(tǒng)調(diào)用創(chuàng)建子進(jìn)程,父進(jìn)程立即終止,保證子進(jìn)程不是控制終端的會(huì)話組首領(lǐng)。

  • 創(chuàng)建新的會(huì)話

子進(jìn)程調(diào)用setsid()來(lái)創(chuàng)建一個(gè)新會(huì)話,成為會(huì)話組首領(lǐng),從而擺脫控制終端和進(jìn)程組。

  • 更改工作目錄

使用chdir("/") 將當(dāng)前工作目錄更改為根目錄,避免守護(hù)進(jìn)程持有任何工作目錄的引用,防止對(duì)文件系統(tǒng)卸載的阻止。

  • 重設(shè)文件權(quán)限掩碼

通過(guò)umask(0) 清除文件權(quán)限掩碼,使得守護(hù)進(jìn)程可以自由設(shè)置文件權(quán)限。

  • 關(guān)閉文件描述符

關(guān)閉繼承自父進(jìn)程的已經(jīng)open的文件描述符(通常是標(biāo)準(zhǔn)輸入、標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)錯(cuò)誤)。

  • 重定向標(biāo)準(zhǔn)輸入/輸出/錯(cuò)誤

重新打開(kāi)標(biāo)準(zhǔn)輸入、輸出和錯(cuò)誤,重定向到/dev/null,以避免守護(hù)進(jìn)程無(wú)意輸出內(nèi)容到不應(yīng)有的地方。

注:fork()系統(tǒng)調(diào)用是一個(gè)較為難理解的調(diào)用,它用于在UNIX/Linux系統(tǒng)中創(chuàng)建一個(gè)新的進(jìn)程。新創(chuàng)建的進(jìn)程被稱(chēng)為子進(jìn)程,它是由調(diào)用fork()的進(jìn)程(即父進(jìn)程)復(fù)制出來(lái)的。子進(jìn)程與父進(jìn)程擁有相同的代碼段、數(shù)據(jù)段、堆和棧,但它們是各自獨(dú)立的進(jìn)程,有不同的進(jìn)程ID (PID)。在父進(jìn)程中,fork()返回子進(jìn)程的PID(正整數(shù)),在子進(jìn)程中,fork()返回0,如果fork()調(diào)用失?。ɡ缦到y(tǒng)資源不足),則返回-1,并設(shè)置errno以指示錯(cuò)誤原因。

下面是一個(gè)符合UNIX標(biāo)準(zhǔn)的守護(hù)進(jìn)程轉(zhuǎn)化函數(shù)的C語(yǔ)言實(shí)現(xiàn),參考了《UNIX環(huán)境高級(jí)編程》中的經(jīng)典步驟:

// daemonize/c/daemon.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <syslog.h>
#include <signal.h>

void daemonize()
{
    pid_t pid;

    // 1. Fork off the parent process 
    pid = fork();
    if (pid < 0) {
        exit(EXIT_FAILURE);
    }
    // If we got a good PID, then we can exit the parent process.
    if (pid > 0) {
        exit(EXIT_SUCCESS);
    }

    // 2. Create a new session to become session leader to lose controlling TTY
    if (setsid() < 0) {
        exit(EXIT_FAILURE);
    }

    // 3. Fork again to ensure the process won't allocate controlling TTY in future
    pid = fork();
    if (pid < 0) {
        exit(EXIT_FAILURE);
    }
    if (pid > 0) {
        exit(EXIT_SUCCESS);
    }

    // 4. Change the current working directory to root.
    if (chdir("/") < 0) {
        exit(EXIT_FAILURE);
    }

    // 5. Set the file mode creation mask to 0.
    umask(0);

    // 6. Close all open file descriptors.
    for (int x = sysconf(_SC_OPEN_MAX); x>=0; x--) {
        close(x);
    }

    // 7. Reopen stdin, stdout, stderr to /dev/null
    open("/dev/null", O_RDWR); // stdin
    dup(0);                    // stdout
    dup(0);                    // stderr

    // Optional: Log the daemon starting
    openlog("daemonized_process", LOG_PID, LOG_DAEMON);
    syslog(LOG_NOTICE, "Daemon started.");
    closelog();
}

int main() {
    daemonize();

    // Daemon process main loop
    while (1) {
        // Perform some background task...
        sleep(30); // Sleep for 30 seconds.
    }

    return EXIT_SUCCESS;
}

注:這里省略了書(shū)中設(shè)置系統(tǒng)信號(hào)handler的步驟。

這里的daemonize函數(shù)完成了標(biāo)準(zhǔn)的守護(hù)化轉(zhuǎn)化過(guò)程,并確保了程序在后臺(tái)無(wú)依賴(lài)地穩(wěn)定運(yùn)行。我們編譯運(yùn)行該程序后,程序進(jìn)入后臺(tái)運(yùn)行,通過(guò)ps命令可以查看到類(lèi)似下面內(nèi)容:

$ ./c-daemon-app 
$ ps -ef|grep c-daemon-app
root     28517     1  0 14:11 ?        00:00:00 ./c-daemon-app

我們看到c-daemon-app的父進(jìn)程是ppid為1的進(jìn)程,即linux的init進(jìn)程。我們看到上面c代碼中轉(zhuǎn)化為守護(hù)進(jìn)程的函數(shù)daemonize進(jìn)行了兩次fork,至于為何要做兩次fork,在我的《理解Zombie和Daemon Process[4]》一文中有說(shuō)明,這里就不贅述了。

那么Go是否可以參考上述步驟實(shí)現(xiàn)Go程序的守護(hù)進(jìn)程轉(zhuǎn)化呢?我們接著往下看。

2. Go語(yǔ)言實(shí)現(xiàn)守護(hù)進(jìn)程的挑戰(zhàn)

關(guān)于Go如何實(shí)現(xiàn)守護(hù)進(jìn)程的轉(zhuǎn)換,在Go尚未發(fā)布1.0之前的2009年就有issue提到,在runtime: support for daemonize[5]中,Go社區(qū)與Go語(yǔ)言的早起元老們討論了在Go中實(shí)現(xiàn)原生守護(hù)進(jìn)程的復(fù)雜性,主要挑戰(zhàn)源于Go的運(yùn)行時(shí)及其線程管理方式。當(dāng)一個(gè)進(jìn)程執(zhí)行fork操作時(shí),只有主線程被復(fù)制到子進(jìn)程中,如果fork前Go程序有多個(gè)線程(及多個(gè)goroutine)在執(zhí)行(可能是由于go runtime調(diào)度goroutine和gc產(chǎn)生的線程),那么fork后,這些非執(zhí)行fork線程的線程(以及goroutine)將不會(huì)被復(fù)制到新的子進(jìn)程中,這可能會(huì)導(dǎo)致后續(xù)子進(jìn)程中線程運(yùn)行的不確定性(基于一些fork前線程留下的數(shù)據(jù)狀態(tài))。

理想情況下是Go runtime提供類(lèi)似的daemonize函數(shù),然后在多線程啟動(dòng)之前實(shí)現(xiàn)守護(hù)進(jìn)程的轉(zhuǎn)化,不過(guò)Go團(tuán)隊(duì)至今也沒(méi)有提供該機(jī)制,而是建議大家使用如systemd的第三方工具來(lái)實(shí)現(xiàn)Go程序的守護(hù)進(jìn)程轉(zhuǎn)化。

既然Go官方不提供方案,Go社區(qū)就會(huì)另辟蹊徑,接下來(lái),我們看看目前Go社區(qū)的守護(hù)進(jìn)程解決方案。

3. Go社區(qū)的守護(hù)進(jìn)程解決方案

盡管面臨挑戰(zhàn),Go社區(qū)還是開(kāi)發(fā)了一些庫(kù)來(lái)支持Go守護(hù)進(jìn)程的實(shí)現(xiàn),其中一個(gè)star比較多的解決方案是github.com/sevlyar/go-daemon。

go-daemon庫(kù)的作者巧妙地解決了Go語(yǔ)言中無(wú)法直接使用fork系統(tǒng)調(diào)用的問(wèn)題。go-daemon采用了一個(gè)簡(jiǎn)單而有效的技巧來(lái)模擬fork的行為:該庫(kù)定義了一個(gè)特殊的環(huán)境變量作為標(biāo)記。程序運(yùn)行時(shí),首先檢查這個(gè)環(huán)境變量是否存在。如果環(huán)境變量不存在,執(zhí)行父進(jìn)程相關(guān)操作,然后使用os.StartProcess(本質(zhì)是fork-and-exec)啟動(dòng)帶有特定環(huán)境變量標(biāo)記的程序副本。如果環(huán)境變量存在,執(zhí)行子進(jìn)程相關(guān)操作,繼續(xù)執(zhí)行主程序邏輯,下面是該庫(kù)作者提供的原理圖:

圖片圖片

這種方法有效地模擬了fork的行為,同時(shí)避免了Go運(yùn)行時(shí)中與線程和goroutine相關(guān)的問(wèn)題。下面是使用go-daemon包實(shí)現(xiàn)Go守護(hù)進(jìn)程的示例:

// daemonize/go-daemon/main.go

package main

import (
 "log"
 "time"

 "github.com/sevlyar/go-daemon"
)

func main() {
 cntxt := &daemon.Context{
  PidFileName: "example.pid",
  PidFilePerm: 0644,
  LogFileName: "example.log",
  LogFilePerm: 0640,
  WorkDir:     "./",
  Umask:       027,
 }

 d, err := cntxt.Reborn()
 if err != nil {
  log.Fatal("無(wú)法運(yùn)行:", err)
 }
 if d != nil {
  return
 }
 defer cntxt.Release()

 log.Print("守護(hù)進(jìn)程已啟動(dòng)")

 // 守護(hù)進(jìn)程邏輯
 for {
  // ... 執(zhí)行任務(wù) ...
  time.Sleep(time.Second * 30)
 }
}

運(yùn)行該程序后,通過(guò)ps可以查看到對(duì)應(yīng)的守護(hù)進(jìn)程:

$make
go build -o go-daemon-app 
$./go-daemon-app 

$ps -ef|grep go-daemon-app
  501  4025     1   0  9:20下午 ??         0:00.01 ./go-daemon-app

此外,該程序會(huì)在當(dāng)前目錄下生成example.pid(用于實(shí)現(xiàn)file lock),用于防止意外重復(fù)執(zhí)行同一個(gè)go-daemon-app:

$./go-daemon-app
2024/09/26 21:21:28 無(wú)法運(yùn)行:daemon: Resource temporarily unavailable

雖然原生守護(hù)進(jìn)程化提供了精細(xì)的控制且無(wú)需安裝和配置外部依賴(lài),但進(jìn)程管理工具提供了額外的功能,如開(kāi)機(jī)自啟[6]、異常退出后的自動(dòng)重啟和日志記錄等,并且Go團(tuán)隊(duì)推薦使用進(jìn)程管理工具來(lái)實(shí)現(xiàn)Go守護(hù)進(jìn)程。進(jìn)程管理工具的缺點(diǎn)在于需要額外的配置(比如systemd)或安裝設(shè)置(比如supervisor)。

4. 小結(jié)

在Go中實(shí)現(xiàn)守護(hù)進(jìn)程化,雖然因?yàn)檎Z(yǔ)言運(yùn)行時(shí)的特性而具有挑戰(zhàn)性,但通過(guò)社區(qū)開(kāi)發(fā)的庫(kù)和謹(jǐn)慎的實(shí)現(xiàn)是可以實(shí)現(xiàn)的。隨著Go語(yǔ)言的不斷發(fā)展,我們可能會(huì)看到更多對(duì)進(jìn)程管理功能的原生支持。同時(shí),開(kāi)發(fā)者可以根據(jù)具體需求,在原生守護(hù)進(jìn)程化、進(jìn)程管理工具或混合方法之間做出選擇。

本文涉及的源碼可以在這里[7]下載。

參考資料

[1] systemd: https://tonybai.com/2016/12/27/when-docker-meets-systemd

[2] supervisor: http://supervisord.org

[3] UNIX環(huán)境高級(jí)編程: https://book.douban.com/subject/25900403/

[4] 理解Zombie和Daemon Process: https://tonybai.com/2005/09/21/understand-zombie-and-daemon-process/

[5] runtime: support for daemonize: https://github.com/golang/go/issues/227

[6] 開(kāi)機(jī)自啟: https://tonybai.com/2022/09/12/how-to-install-a-go-app-as-a-system-service-like-gitlab-runner

[7] 這里: https://github.com/bigwhite/experiments/tree/master/daemonize

[8] Gopher部落知識(shí)星球: https://public.zsxq.com/groups/51284458844544

[9] 鏈接地址: https://m.do.co/c/bff6eed92687

責(zé)任編輯:武曉燕 來(lái)源: TonyBai
相關(guān)推薦

2024-08-29 13:23:04

WindowsGo語(yǔ)言

2010-06-28 14:52:30

cron進(jìn)程

2011-03-29 12:45:07

Zabbix進(jìn)程

2017-04-11 16:00:40

Linuxsyslog進(jìn)程

2010-03-02 16:37:53

Linux Quagg

2010-03-16 13:41:09

Python進(jìn)程

2009-11-24 11:35:59

2010-07-15 15:54:10

Perl守護(hù)進(jìn)程

2024-02-21 08:33:27

GoReadDir性能

2023-11-30 08:09:02

Go語(yǔ)言

2012-11-08 09:36:10

Google Go

2012-05-08 11:01:45

linux守護(hù)進(jìn)程

2013-01-15 15:18:46

Linux守護(hù)進(jìn)程

2015-10-20 17:06:52

2021-07-26 09:47:38

Go語(yǔ)言C++

2010-07-15 15:47:46

Perl守護(hù)進(jìn)程

2011-08-08 10:02:55

iPhone開(kāi)發(fā) 進(jìn)程 通信

2018-01-02 16:39:04

2020-06-02 16:19:09

華為

2021-07-15 12:44:25

Shell編程進(jìn)程
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)