Search code examples
goserial-portgoroutine

How to correctly use synchronization mechanisms to read input from serial port


I am currently reading in input from an Arduino with the serial port using two goroutines.

The purpose of these routines is so the first routine can read in the input and output the data, then the second routine does the same. It does not matter which routines executes when, I want one goroutine to execute once the other go routine is done executing. This is my code:

package main

import (
    "log"
    "github.com/tarm/serial"
    "bufio"
    "sync"
    "fmt"
)


func readFirstLine(scanner *bufio.Scanner, port *serial.Port, wg *sync.WaitGroup){
    defer wg.Done()
    for scanner.Scan() {
        fmt.Println("\n", scanner.Text())
    }    
}

func readSecondLine(scanner *bufio.Scanner, port *serial.Port, wg *sync.WaitGroup){

    defer wg.Done()
    for scanner.Scan() {
        fmt.Println("\n", scanner.Text())
    }
}

func main() {
    usbRead := &serial.Config{Name: "COM5", Baud: 9600, ReadTimeout: 0}
    port, err := serial.OpenPort(usbRead)
    var wg sync.WaitGroup
    wg.Add(2)


    if err != nil {
        log.Fatal(err)
    }

    scanner := bufio.NewScanner(port)

    for {
    go readFirstLine(scanner, port, &wg)
    go readSecondLine(scanner, port, &wg)
    wg.Wait()

    }
}

This is the expected output (since there are only three lines of data, I want each of the goroutines to read and output the same three lines each time - this will be looped):

{"temperature":[27.7],"humidity":[46.9],"sensor":"DHT22"}
{"temperature":[25.41545],"sensor":"LM35DZ"}
{"blink":["true"],"actuator":"arduinoLED"}
{"temperature":[27.7],"humidity":[46.9],"sensor":"DHT22"}
{"temperature":[25.41545],"sensor":"LM35DZ"}
{"blink":["true"],"actuator":"arduinoLED"}

However, this is the actual output:

,46.[25.41545,25.41545,25.41545],"sensor":"LM35DZ"}
 {"blink":["true","true","true"],"actuato ":"arduinoLED"}
 {"tempertemperature":[25.41545,25.41545,25.41545],"sensor":"LM35DZ"}
 {"blink":["true","true","true"],"actuator":"arduinoLED"}
 {"tempert"eep uratr:" [hum.7],"t  idiy[: "se],n  :"Dr"H  }
 2"{  perema  ":[re2  54541]  nsoser  LM3"5 

As you can see, there is conflict in the data.

My question is: I know you have to use some sort of synchronization mechanism to make sure one go routine will execute after the first goroutine is done executing. I am new to this language (and reading from serial ports) so I am not quite sure if I used sync.WaitGroup correctly to achieve the desired results. I would love to know how I can fix this issue, or if I should use a different synchronization mechanism and how to correctly use it.


Solution

  • A sync.WaitGroup as used here will ensure that your main goroutine does not exit before the async workers have finished their work.

    But as you've correctly noticed there's no current means of enforcing the order the goroutines are executing in. You could use a "token" based channel; imagine passing a ball, you only get to work when holding the ball.

    func readFirstLine(scanner *bufio.Scanner, port *serial.Port, wg *sync.WaitGroup, ch chan<- struct{}){
        defer wg.Done()
        defer func () {ch <-struct {}{}}() // write a token to the channel when done
        for scanner.Scan() {
            fmt.Println("\n", scanner.Text())
        }
    }
    
    func readSecondLine(scanner *bufio.Scanner, port *serial.Port, wg *sync.WaitGroup, ch <- chan struct{}){
        <-ch // don't start work until the notification / token is received
        defer wg.Done()
        for scanner.Scan() {
            fmt.Println("\n", scanner.Text())
        }
    }
    
    func main() {
        // trimmed ...
    
        ch := make(chan struct{})
    
        for {
            go readFirstLine(scanner, port, &wg, ch)
            go readSecondLine(scanner, port, &wg, ch)
            wg.Wait()
    
        }
    }
    

    But: as pointed out in the comments, you are not allowing goroutine2 to proceed until goroutine1 has finished executing, this means you are enforcing a "serial" constraint on concurrent program structure.

    Instead you could remove the concurrency by removing go and sync.WaitGroup and just call the functions in order, as this is what it appears you are trying to achieve.