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 ofgo 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
Tutorials
+13.2k Golang : Read from buffered reader until specific number of bytes
+6.9k Golang : Array mapping with Interface
+44.4k Golang : Use wildcard patterns with filepath.Glob() example
+9.2k Golang : Convert(cast) string to int64
+6.4k Elasticsearch : Shutdown a local node
+5.1k Golang : Pad file extension automagically
+14.2k Golang : Find network of an IP address
+16.8k Golang : Get number of CPU cores
+7.1k Golang : Scanf function weird error in Windows
+8.8k Golang : Capture text return from exec function example
+14.8k Golang : Save(pipe) HTTP response into a file
+10.2k Swift : Convert (cast) String to Integer