一、Go并发

1、Go两种并发编程模型

Go语言中的并发程序可以用两种手段来实现:

第8章:基于Goroutines和Channels的并发

  • 第8章讲解goroutine和channel,其支持“顺序通信进程”(communicating sequential processes)或被简称为CSP。
  • CSP是一种现代的并发编程模型,在这种编程模型中值会在不同的运行实例(goroutine)中传递,尽管大多数情况下仍然是被限制在单一实例中。

第9章:基于共享变量/内存的并发

  • 更传统并发编程模型,多线程共享内存。如果你在其它的主流语言中写过并发程序的话可能会更熟悉一些。
  • 第9章也会深入介绍一些并发程序带来的风险和陷阱。
    • 尽管Go对并发的支持是众多强力特性之一,但跟踪调试并发程序还是很困难,在线性程序中形成的直觉往往还会使我们误入歧途。

2、基于goroutine的并发

(1)goroutine本质

(2)goroutine特点

(3)区分:goroutine vs 多线程
Java/C++ 的多线程是手动管理的,需要开发者维护一个线程池,手动包装一个又一个任务,并且需要手动调度线程。
代码里调用操作系统的接口,创建线程、切换线程。

Go的多线程是自动管理的,在用户态模拟多线程。goroutine就是这样一种机制,程序员只需要自己定义任务,系统自动帮助我们把任务分配到CPU上并发执行。
Go语言自己在用户的runtime里面,自己实现了一堆用户态的线程,也就是用程序模拟的线程。这些线程最终会调用操作系统线程。切换时,Go语言是自动切换。
Go的并发通过goroutine实现,goroutine相当于线程,属于用户态的线程。
goroutine很轻量级,轻轻松松开十几万个并发工作。Java中的一个线程至少占用2M的空间,开1000个线程就至少要2G空间,16G内存最多也就8k个线程,十几万个早崩了。

3、基于共享变量的并发

二、Go多线程通信实现

参考:
并发的clock服务(服务器向客户端写):https://docs.hacknode.org/gopl-zh/ch8/ch8-02.html
多线程服务端和客户端通信(客户端向服务器写):https://blog.51cto.com/u_15144024/2858043

1、功能

多个客户端,向服务器请求时钟,
①服务器回复当前日期时间。
②服务器每隔一秒钟发送一次当前时间。
③服务器每个整点报时。

2、代码

①服务器回复当前日期时间。

func Test14() {
	p := "8888"
	go clockServer(1, p)

	// 单客户端
	log.Println("client:", clockClient(p))

	// 多客户端
	go func() {
		for i := 1; i < 4; i++ {
			time.Sleep(3 * time.Second)
			log.Println("client", i, ":", clockClient(p))
		}
	}()

	time.Sleep(1 * time.Minute) //睡1分钟,保证前面的线程都执行完了,否则可能没执行完就被主线程强行终止了。
}

②服务器每隔一秒钟发送一次当前时间。

func Test15() {
	p := "8888"
	go clockServer(2, p)

	// 单客户端
	log.Println("client:", clockClient(p))
}

③服务器每个整点报时。


公共代码:

func clockClient(port string) (result string) {
	conn, err := net.Dial("tcp", "localhost:"+port)
	if err != nil {
		log.Fatal(err)
	}
	defer conn.Close()
	// 服务器向客户端写
	buf := make([]byte, 128)
	_, err = io.CopyBuffer(os.Stdout, conn, buf) //同上,加个buffer
	// _, err = conn.Read(buf) //读。读出来后conn里就没有了。【这个不能用??】
	if err != nil {
		log.Fatal(err)
	}
	log.Println("服务器发过来的当前时间:", string(buf))
	return string(buf)
}


func clockServer(serverType int, port string) {
	listener, err := net.Listen("tcp", "localhost:"+port)
	if err != nil {
		log.Fatal(err)
	}
	defer listener.Close()

	switch serverType {
	case 1:
		for {
			conn, err := listener.Accept()
			if err != nil {
				log.Fatal(err)
			}
			t := time.Now().Format("Mon Jan 2 15:04:05")
			_, err = conn.Write([]byte(t))
			if err != nil {
				return
			}
		}
	case 2:
		for {
			conn, err := listener.Accept()
			if err != nil {
				log.Fatal(err)
			}
			defer conn.Close()
			for i := 0; i < 3; i++ { // 一个线程服务3秒钟,然后换下一个线程
				t := time.Now().Format("Mon Jan 2 15:04:05\n")
				_, err = conn.Write([]byte(t))
				// _, err := io.WriteString(conn, t) // 同上
				if err != nil {
					return
				}
				time.Sleep(1 * time.Second)
			}
		}
	case 3:
		for {
			conn, err := listener.Accept()
			if err != nil {
				log.Fatal(err)
			}
			for { //这如果不是监听的话,就一直占着CPU吧??这不是死等吗?有问题吧?
				t := time.Now().Format("Mon Jan 2 15:04:05")
				// m := time.Now().Minute()
				s := time.Now().Second()
				// if m == 0 && s == 0 { //整时
				if s == 0 { //整分
					_, err = conn.Write([]byte(t))
					if err != nil {
						return
					}
				}
				// time.Sleep(1 * time.Minute)
			}
		}
	}
}
posted on 2022-01-06 19:31  西伯尔  阅读(284)  评论(0编辑  收藏  举报