Golang : Multi threading or run two processes or more example




Problem:

You want to run two or more processes(multi-threading) in Golang. For example, you want to run a web server and at the same time, the web server executes a goroutine that polls data from another source or writes to a log file. How to do that?

Solution:

One of the most common myth floating around in Golang universe is that it lacks multithreading feature. This myth needs to be debunked, simply because Golang multiplexes goroutines on pthreads (POSIX-thread) and this should suffice for "creating multi threading program". We are going to enhance the previous example of daemonizing a web server example to demonstrate how to "create a multi-threaded program in Golang."

For the code below, you can start by ignoring the rest, but pay attention to lines with:

// PROCESS NUMBER ONE <-----------

and

// PROCESS NUMBER TWO <-----------

Here you go!

multithread.go

 package main

 import (
 "fmt"
 "io/ioutil"
 "log"
 "net/http"
 "os"
 "os/exec"
 "os/signal"
 "strconv"
 "strings"
 "syscall"
 "time"
 )

 var PIDFile = "/tmp/daemonize.pid"

 func savePID(pid int) {

 file, err := os.Create(PIDFile)
 if err != nil {
 log.Printf("Unable to create pid file : %v\n", err)
 os.Exit(1)
 }

 defer file.Close()

 _, err = file.WriteString(strconv.Itoa(pid))

 if err != nil {
 log.Printf("Unable to create pid file : %v\n", err)
 os.Exit(1)
 }

 file.Sync() // flush to disk

 }

 func sayHelloWorld(w http.ResponseWriter, r *http.Request) {
 html := "Hello World"

 w.Write([]byte(html))
 }

 func echoToLog(n time.Duration) {
 for _ = range time.Tick(n * time.Second) {
 str := "Still alive..."
 log.Println(str)
 }
 }

 func main() {

 var port = ":8080"

 var logEchoFile = "log.txt"

 logFile, err := os.OpenFile(logEchoFile, os.O_CREATE|os.O_WRONLY, 0666)

 if err != nil {
 panic(err)
 }

 defer logFile.Close()

 // direct all log messages to logEchoFile
 log.SetOutput(logFile)

 if len(os.Args) != 2 {
 fmt.Printf("Usage : %s [start|stop] \n ", os.Args[0]) // return the program name back to %s
 os.Exit(0) // graceful exit
 }

 if strings.ToLower(os.Args[1]) == "main" {

 // PROCESS NUMBER ONE <-----------

 // Make arrangement to remove PID file upon receiving the SIGTERM from kill command
 ch := make(chan os.Signal, 1)
 signal.Notify(ch, os.Interrupt, os.Kill, syscall.SIGTERM)

 go func() {
 signalType := <-ch
 signal.Stop(ch)
 log.Println("Exit command received. Exiting...")

 // this is a good place to flush everything to disk
 // before terminating.
 log.Println("Received signal type : ", signalType)

 // remove PID file
 os.Remove(PIDFile)

 os.Exit(0)

 }()

 // PROCESS NUMBER TWO <-----------
 go echoToLog(1) // tail -f log.txt to see output every 1 second

 mux := http.NewServeMux()
 mux.HandleFunc("/", sayHelloWorld)
 log.Fatalln(http.ListenAndServe(port, mux))

 // IMPORTANT!! goroutine after ListenAndServe will not work
 //go echoToLog()
 }

 if strings.ToLower(os.Args[1]) == "start" {

 // check if daemon already running.
 if _, err := os.Stat(PIDFile); err == nil {
 fmt.Println("Already running or /tmp/daemonize.pid file exist.")
 os.Exit(1)
 }

 cmd := exec.Command(os.Args[0], "main")
 cmd.Start()
 fmt.Println("Daemon process ID is : ", cmd.Process.Pid)
 fmt.Println("Listerning and Serving HTTP request at port ", port)
 fmt.Println("Echoing logs to ", logEchoFile)
 savePID(cmd.Process.Pid)
 os.Exit(0)

 }

 // upon receiving the stop command
 // read the Process ID stored in PIDfile
 // kill the process using the Process ID
 // and exit. If Process ID does not exist, prompt error and quit

 if strings.ToLower(os.Args[1]) == "stop" {
 if _, err := os.Stat(PIDFile); err == nil {
 data, err := ioutil.ReadFile(PIDFile)
 if err != nil {
 fmt.Println("Not running")
 os.Exit(1)
 }
 ProcessID, err := strconv.Atoi(string(data))

 if err != nil {
 fmt.Println("Unable to read and parse process id found in ", PIDFile)
 os.Exit(1)
 }

 process, err := os.FindProcess(ProcessID)

 if err != nil {
 fmt.Printf("Unable to find process ID [%v] with error %v \n", ProcessID, err)
 os.Exit(1)
 }
 // remove PID file
 os.Remove(PIDFile)

 fmt.Printf("Killing process ID [%v] now.\n", ProcessID)
 // kill process and exit immediately
 err = process.Kill()

 if err != nil {
 fmt.Printf("Unable to kill process ID [%v] with error %v \n", ProcessID, err)
 os.Exit(1)
 } else {
 fmt.Printf("Killed process ID [%v]\n", ProcessID)
 os.Exit(0)
 }

 } else {

 fmt.Println("Not running.")
 os.Exit(1)
 }
 } else {
 fmt.Printf("Unknown command : %v\n", os.Args[1])
 fmt.Printf("Usage : %s [start|stop]\n", os.Args[0]) // return the program name back to %s
 os.Exit(1)
 }

 }

use go build multithread.go instead of go run

execute the program with ./multithread start command

At this stage, the program self-terminate and stays in memory as daemonized process. Point your browser to localhost:8080 and on your terminal execute tail -f log.txt

There you go! An example of"multi-threaded" program in Golang. Which...actually multiplexed goroutines in action. A simpler and better way IMHO.

References:

https://en.wikipedia.org/wiki/POSIX_Threads

https://www.socketloop.com/tutorials/golang-daemonizing-a-simple-web-server-process-example

  See also : Golang : Daemonizing a simple web server process example





By Adam Ng

IF you gain some knowledge or the information here solved your programming problem. Please consider donating to the less fortunate or some charities that you like. Apart from donation, planting trees, volunteering or reducing your carbon footprint will be great too.


Advertisement