Concurrent Clock Server#
Let's implement a concurrent clock server.
|--client|--client.go|--server|--server.gogo.modmain.go
Write a simple TCP server listening on port 8080.
// haimtran 19/05/2024// concurrent clock serverpackage mainimport ("fmt""io""log""net""time")func main() {fmt.Println("Hello World")// tcp listenerlistener, err := net.Listen("tcp", ":8080")if err != nil {log.Print(err)}// infinite loop to handle requestfor {conn, err := listener.Accept()if err != nil {log.Print(err)}// each request handled by a goroutinego handle(conn)}}func handle(conn net.Conn) {defer conn.Close()for {_, error := io.WriteString(conn, time.Now().Format("15:04:05\n"))if error != nil {log.Print(error)return}time.Sleep(2 * time.Second)}}
Write a simple client dial on port 8080.
package mainimport ("io""log""net""os")func main() {conn, err := net.Dial("tcp", "localhost:8080")if err != nil {log.Fatal(err)}defer conn.Close()mustCopy(os.Stdout, conn)}func mustCopy(dst io.Writer, src io.Reader) {if _, err := io.Copy(dst, src); err != nil {log.Fatal(err)}}
Alternatively, we can use netcat.
nc localhost 8080
Unbuffered Channel#
Quote from the book the go programming.
A send operation on an unbuffered channel blocks the sending goroutine until another goroutine executes a corresponding receive on the same channel, at which point the value is transmitted and both goroutines is blocked until another goroutine performs a send on the same channel.
func helloUnbufferChannel() {// create a channel of integerch := make(chan int)// goroutine write to chgo func() {ch <- 42ch <- 43ch <- 44}()// wait until the write channel completefmt.Println(<-ch)fmt.Println(<-ch)fmt.Println(<-ch)// close channel}
Use close to close a channel.
func helloChannel() {// create an unbuffered channelc := make(chan int)go func() {// time add delay of 2 secondstime.Sleep(2 * time.Second)fmt.Println("Hello from goroutine")// write to channelc <- 42//c <- 100// close channelclose(c)}()// wait until the write channel completefor v := range c {fmt.Println(v)}}
Buffered Channel#
Quoted from the go programming.
A buffered channel has a queue of elements. The queue's maximum size is determined when it is created, by the capacity argument to make... The channel buffer decouples the sending and receiving goroutines.
func ImageFile(infile string) (string, error) {time.Sleep(2 * time.Second)fmt.Println("infile", infile)return infile, nil}// goroutine leak and buffered channel// page 237 the go programmingfunc makeThumbnails5(filenames []string) (thumbfiles []string, err error) {type item struct {thumbfile stringerr error}ch := make(chan item, len(filenames))for _, f := range filenames {go func(f string) {var it itemit.thumbfile, it.err = ImageFile(f)ch <- it}(f)}for range filenames {it := <-chif it.err != nil {return nil, it.err}thumbfiles = append(thumbfiles, it.thumbfile)}return thumbfiles, nil}
Wait Group#
// unpredictable number of filnames// use channel for filenamesfunc makeThumbnails6(filenames <-chan string) int64 {sizes := make(chan int64)var wg sync.WaitGroup // number of goroutines to wait forfor f := range filenames {wg.Add(1)// worker goroutinego func(f string) {defer wg.Done()thumb, err := ImageFile(f)if err != nil {return}info, _ := os.Stat(thumb)sizes <- info.Size()}(f)}// closergo func() {wg.Wait()close(sizes)}()//var total int64for size := range sizes {total += size}return total}
main.go
// haimtran 19/05/2024// channel and goroutinespackage mainimport ("fmt""os""sync""time")// unbuffered channel and synchfunc helloChannel() {// create an unbuffered channelc := make(chan int)go func() {// time add delay of 2 secondstime.Sleep(2 * time.Second)fmt.Println("Hello from goroutine")// write to channelc <- 42//c <- 100// close channelclose(c)}()// wait until the write channel completefor v := range c {fmt.Println(v)}}//func helloUnbufferChannel() {// create a channel of integerch := make(chan int)// goroutine write to chgo func() {ch <- 42ch <- 43ch <- 44}()// wait until the write channel completefmt.Println(<-ch)fmt.Println(<-ch)fmt.Println(<-ch)// close channel}func ImageFile(infile string) (string, error) {time.Sleep(2 * time.Second)fmt.Println("infile", infile)return infile, nil}// goroutine leak and buffered channel// page 237 the go programmingfunc makeThumbnails5(filenames []string) (thumbfiles []string, err error) {type item struct {thumbfile stringerr error}ch := make(chan item, len(filenames))for _, f := range filenames {go func(f string) {var it itemit.thumbfile, it.err = ImageFile(f)ch <- it}(f)}for range filenames {it := <-chif it.err != nil {return nil, it.err}thumbfiles = append(thumbfiles, it.thumbfile)}return thumbfiles, nil}// unpredictable number of filnames// use channel for filenamesfunc makeThumbnails6(filenames <-chan string) int64 {sizes := make(chan int64)var wg sync.WaitGroup // number of goroutines to wait forfor f := range filenames {wg.Add(1)// worker goroutinego func(f string) {defer wg.Done()thumb, err := ImageFile(f)if err != nil {return}info, _ := os.Stat(thumb)sizes <- info.Size()}(f)}// closergo func() {wg.Wait()close(sizes)}()//var total int64for size := range sizes {total += size}return total}func main() {filenames := []string{"a.jpg", "b.jpg", "c.jpg"}fmt.Println("Hello")// hello channel// helloChannel()// helloUnbufferChannel()makeThumbnails5(filenames)}