golang从入门到放弃【四】

Goroutine

goroutine是由Go的运行时调度和管理的。程序会智能地将 goroutine 中的任务合理地分配给每个CPU。Go在语言层面已经内置了调度和上下文切换的机制。

使用

在调用函数的时候在前面加上go关键字,就可以为一个函数创建一个goroutine。一个goroutine必定对应一个函数,可以创建多个goroutine去执行相同的函数。

func main() {
	for i := 0; i < 10; i++ {
		go func() {
			fmt.Println("go goroutine2 execute" + strconv.Itoa(i))
		}()
	}

	fmt.Println("go main execute")
	time.Sleep(time.Second)
}

OS线程(操作系统线程)一般都有固定的栈内存(通常为2MB),一个goroutine的栈在其生命周期开始时只有很小的栈(典型情况下2KB),goroutine的栈按需增大和缩小,goroutine的栈大小限制可以达到1GB。

GMP

GPM是go语言自己实现的一套调度系统。是在go运行时层面实现的,区别于操作系统调度OS线程。

  • G就是个goroutine的,里面除了存放本goroutine信息外 还有与所在P的绑定等信息。
  • P管理着一组goroutine队列,P里面会存储当前goroutine运行的上下文环境(函数指针,堆栈地址及地址边界),P会对自己管理的goroutine队列做一些调度(比如把占用CPU时间较长的goroutine暂停、运行后续的goroutine等等)当自己的队列消费完了就去全局队列里取,如果全局队列里也消费完了会去其他P的队列里抢任务。
  • M是Go运行时(runtime)对操作系统内核线程的虚拟, M与内核线程一般是一一映射的关系, 一个groutine最终是要放到M上执行的。

P与M一般也是一一对应的。他们关系是: P管理着一组G挂载在M上运行。当一个G长久阻塞在一个M上时,runtime会新建一个M,阻塞G所在的P会把其他的G 挂载在新建的M上。当旧的G阻塞完成或者认为其已经死掉时 回收旧的M。

P的个数是通过runtime.GOMAXPROCS设定(最大256),默认为物理线程数。

goroutine的调度是在用户态下完成的, 不涉及内核态与用户态之间的频繁切换,包括内存的分配与释放,都是在用户态维护着一块大的内存池, 不直接调用系统的malloc函数(除非内存池需要改变),成本比调度OS线程低很多。 另一方面充分利用了多核的硬件资源,近似的把若干goroutine均分在物理线程上。

runtime

runtime.Gosched()

让出CPU时间片,重新等待安排任务。

func main() {
	go test("贝塔")

	runtime.Gosched()
	for i := 0; i < 2; i++ {
		fmt.Println("main print!")
	}
}

func test(str string) {
	fmt.Printf("test print :%s\n", str)
}
runtime.Goexit()

退出当前协程

// B.defer
// A.defer
func main() {
	go func() {
		defer fmt.Println("A.defer")
		func() {
			defer fmt.Println("B.defer")
			runtime.Goexit()
			defer fmt.Println("c.defer")
			fmt.Println("B")
		}()
		fmt.Println("A")
	}()
	for {
	}
}
runtime.GOMAXPROCS()

Go运行时的调度器使用GOMAXPROCS参数来确定需要使用多少个OS线程来同时执行Go代码。默认值是机器上的CPU核心数。

func main() {
	runtime.GOMAXPROCS(2)
	go a()
	go b()
	time.Sleep(time.Second)
}

func a() {
	for i := 0; i < 10; i++ {
		fmt.Printf("A:%d\n", i)
	}
	fmt.Printf("A  time :%v\n", time.Now())
}

func b() {
	for i := 0; i < 10; i++ {
		fmt.Printf("B:%d\n", i)
	}
	fmt.Printf("B  time :%v\n", time.Now())
}

channel

Go语言的并发模型是CSP(Communicating Sequential Processes),提倡通过通信共享内存而不是通过共享内存而实现通信。

goroutine是Go程序并发的执行体,channel就是它们之间的连接。channel是可以让一个goroutine发送特定值到另一个goroutine的通信机制。

Go 语言中的通道是一种特殊的类型。通道像一个传送带或者队列,总是遵循先入先出的规则,保证收发数据的顺序。每一个通道都是一个具体类型的导管,也就是声明channel的时候需要为其指定元素类型。

channel是一种引用类型。

创建

通道是引用类型,通道类型的空值是nil。声明通道后需要使用make函数初始化之后才能使用。

func main() {
	var ch chan int
	fmt.Println(ch)
	ch = make(chan int, 3)
	fmt.Println(ch)
}

操作

通道有发送(send)、接收(receive)和关闭(close)三种操作。

发送和接收都使用<-符号。通过调用内置的close函数来关闭通道。

通道是可以被垃圾回收机制回收的,关闭后的通道有以下特点:

  1. 对一个关闭的通道再发送值就会导致panic。
  2. 对一个关闭的通道进行接收会一直获取值直到通道为空。
  3. 对一个关闭的并且没有值的通道执行接收操作会得到对应类型的零值。
  4. 关闭一个已经关闭的通道会导致panic。
ch := make(chan int)
ch <- 10 // 把10发送到ch中
x := <- ch // 从ch中接收值并赋值给变量x
<-ch       // 从ch中接收值,忽略结果
close(ch)

无缓冲通道

无缓冲的通道又称为阻塞的通道。无缓冲的通道只有在有人接收值的时候才能发送值。

无缓冲通道上的发送操作会阻塞,直到另一个goroutine在该通道上执行接收操作,这时值才能发送成功,两个goroutine将继续执行。相反同理。因此无缓冲通道也被称为同步通道。

func main() {
	var c chan int = make(chan int)
	go reveice(c)
	c <- 999
	fmt.Println("发送成功")
}

func reveice(c chan int) {
	a := <-c
	fmt.Println("接收成功!", a)
}

有缓冲通道

只要通道的容量大于零,那么该通道就是有缓冲的通道,通道的容量表示通道中能存放元素的数量。可以使用内置的len函数获取通道内元素的数量,使用cap函数获取通道的容量。

func main() {
	var ch = make(chan int, 2)
	ch <- 666
	go func(c chan int) {
		a := <-c
		fmt.Println("接收成功", a)
	}(ch)
	fmt.Println("发送成功")
	time.Sleep(time.Second)
}

close()

通过内置的close()函数关闭channel。关闭已经关闭的channel会引发panic。

func main() {
	ch := make(chan int)
	go func(c chan int) {
		for i := 0; i < 10; i++ {
			c <- i
		}
		close(c)
	}(ch)

	for {
		if a, ok := <-ch; ok {
			fmt.Println(a)
		} else {
			break
		}
	}
}

取值

可以通过<-或者for range 取值并判断通道是否关闭。

func main() {
	var ch = make(chan int)
	ch2 := make(chan int)
	go func() {
		for i := 0; i < 100; i++ {
			ch <- i
		}
		close(ch)
	}()

	go func() {
		for {
			a, ok := <-ch
			if !ok {
				break
			}

			ch2 <- a * 2
		}
		close(ch2)
	}()

	for n := range ch2 {
		fmt.Println(n)
	}
}

单向通道

有时会将通道作为参数在多个任务函数间传递,在不同的任务函数中使用通道都会对其进行限制在函数中只能发送或只能接收。

在函数传参及任何赋值操作中将双向通道转换为单向通道是可以的,但反过来是不可以的。

  1. chan<- int是一个只能发送的通道,可以发送但是不能接收;
  2. <-chan int是一个只能接收的通道,可以接收但是不能发送。
func main() {
	ch1 := make(chan int)
	ch2 := make(chan int)
	go set(ch1)
	go execute(ch1, ch2)
	for i := range ch2 {
		fmt.Println(i)
	}
}

func set(c1 chan<- int) {
	for i := 0; i < 30; i++ {
		c1 <- i
	}
	close(c1)
}

func execute(c1 <-chan int, c2 chan<- int) {
	for n := range c1 {
		c2 <- n * n
	}
	close(c2)
}

select

select的使用类似于switch语句,它有一系列case分支和一个默认的分支。每个case会对应一个通道的通信(接收或发送)过程。select会一直等待,直到某个case的通信操作完成时,就会执行case分支对应的语句。

select可以同时监听一个或多个channel,直到其中一个channel ready

func main() {
	c1 := make(chan string)
	c2 := make(chan string)
	go test1(c1)
	go test2(c2)

	select {
	case s1 := <-c1:
		fmt.Println(s1)
	case s2 := <-c2:
		fmt.Println(s2)
	}
}

func test1(c chan string) {
	time.Sleep(2 * time.Second)
	c <- "舒克"
}

func test2(c chan string) {
	time.Sleep(5 * time.Second)
	c <- "贝塔"
}

如果多个channel同时ready,则随机选择一个执行

func main() {
	intChan := make(chan int)
	stringChan := make(chan string)

	go func() {
		intChan <- 1
	}()
	go func() {
		stringChan <- "A"
	}()

	select {
	case i := <-intChan:
		fmt.Printf("%d\n", i)
	case s := <-stringChan:
		fmt.Printf("%s\n", s)
	}
}

用于判断管道是否存满

func main() {
	strChan := make(chan string, 10)
	go input(strChan)

	for s := range strChan {
		fmt.Println(s)
		time.Sleep(time.Second)
	}
}

func input(in chan<- string) {
	for {
		select {
		case in <- "Hello World!":
			fmt.Println("input success")
		default:
			fmt.Println("input full")
		}
		time.Sleep(500 * time.Millisecond)
	}
}

WaitGroup

类似CountDownLatch

var X int
var wg sync.WaitGroup

func add() {
	for i := 0; i < 1000; i++ {
		X++
	}
	wg.Done()
}

func main() {
	wg.Add(2)
	go add()
	go add()
	wg.Wait()
	fmt.Println(X)
}

Mutex

Mutex类型来实现互斥锁,多个goroutine同时等待一个锁时,唤醒的策略是随机的。

var X int
var wg sync.WaitGroup
var lock sync.Mutex

func add() {
	for i := 0; i < 1000; i++ {
		lock.Lock()
		X++
		lock.Unlock()
	}
	wg.Done()
}

func main() {
	wg.Add(2)
	go add()
	go add()
	wg.Wait()
	fmt.Println(X)
}

RWMutex

读写锁分为两种:读锁和写锁。当一个goroutine获取读锁之后,其他的goroutine如果是获取读锁会继续获得锁,如果是获取写锁就会等待;当一个goroutine获取写锁之后,其他的goroutine无论是获取读锁还是写锁都会等待。

var n int
var RWLock sync.RWMutex
var Lock sync.Mutex
var Wg sync.WaitGroup

// 0:lock  other:RWLock
var flag = 1

func read() {
	if flag == 0 {
		Lock.Lock()
	} else {
		RWLock.RLock()
	}
	// 读取耗时1ms
	time.Sleep(time.Millisecond)
	if flag == 0 {
		Lock.Unlock()
	} else {
		RWLock.RUnlock()
	}
	Wg.Done()
}

func write() {
	if flag == 0 {
		Lock.Lock()
	} else {
		RWLock.Lock()
	}
	// 写耗时10ms
	n++
	time.Sleep(10 * time.Millisecond)
	if flag == 0 {
		Lock.Unlock()
	} else {
		RWLock.Unlock()
	}
	Wg.Done()
}

func main() {
	start := time.Now()
	for i := 0; i < 1000; i++ {
		Wg.Add(1)
		go read()
	}
	for i := 0; i < 10; i++ {
		Wg.Add(1)
		go write()
	}
	Wg.Wait()
	end := time.Now()
	fmt.Printf("end  n: %d  time:%v", n, end.Sub(start))
}

atomic

代码中的加锁操作因为涉及内核态的上下文切换会比较耗时、代价比较高。针对基本数据类型我们还可以使用原子操作来保证并发安全,因为原子操作是Go语言提供的方法它在用户态就可以完成,因此性能比加锁操作更好。

var a int64
var cdl sync.WaitGroup
var l sync.Mutex

func main() {
	start := time.Now()
	for i := 0; i < 100000; i++ {
		cdl.Add(1)
		//go mutexAdd()
		go atomicAdd()
	}
	cdl.Wait()
	end := time.Now()
	fmt.Printf("end a:%d time:%v", a, end.Sub(start))
}

func atomicAdd() {
	atomic.AddInt64(&a, 1)
	cdl.Done()
}

func mutexAdd() {
	l.Lock()
	a++
	l.Unlock()
	cdl.Done()
}

GMP

  • 全局队列(Global Queue):存放等待运行的 G。
  • P 的本地队列:同全局队列类似,存放的也是等待运行的 G,存的数量有限,不超过 256 个。新建 G’时,G’优先加入到 P 的本地队列,如果队列满了,则会把本地队列中一半的 G 移动到全局队列。
  • P 列表:所有的 P 都在程序启动时创建,并保存在数组中,最多有 GOMAXPROCS(可配置) 个。
  • M:线程想运行任务就得获取 P,从 P 的本地队列获取 G,P 队列为空时,M 也会尝试从全局队列拿一批 G 放到 P 的本地队列,或从其他 P 的本地队列偷一半放到自己 P 的本地队列。M 运行 G,G 执行之后,M 会从 P 获取下一个 G,不断重复下去。

调度器的设计策略

  • work stealing 机制

当本线程无可运行的 G 时,尝试从其他线程绑定的 P 偷取 G,而不是销毁线程。

  • hand off 机制

当本线程因为 G 进行系统调用阻塞时,线程释放绑定的 P,把 P 转移给其他空闲的线程执行。

go func () 调度流程

  1. 通过 go func () 来创建一个 goroutine;
  2. 有两个存储 G 的队列,一个是局部调度器 P 的本地队列、一个是全局 G 队列。新创建的 G 会先保存在 P 的本地队列中,如果 P 的本地队列已经满了就会保存在全局的队列中;
  3. G 只能运行在 M 中,一个 M 必须持有一个 P,M 与 P 是 1:1 的关系。M 会从 P 的本地队列弹出一个可执行状态的 G 来执行,如果 P 的本地队列为空,就会想其他的 MP 组合偷取一个可执行的 G 来执行;
  4. 一个 M 调度 G 执行的过程是一个循环机制;
  5. 当 M 执行某一个 G 时候如果发生了 syscall 或则其余阻塞操作,M 会阻塞,如果当前有一些 G 在执行,runtime 会把这个线程 M 从 P 中摘除 (detach),然后再创建一个新的操作系统的线程 (如果有空闲的线程可用就复用空闲线程) 来服务于这个 P;
  6. 当 M 系统调用结束时候,尝试获取一个空闲的 P 执行。如果获取不到 P,那么这个线程 M 变成休眠状态, 加入到空闲线程中。

网络编程

TCP

TCP/IP是一种面向连接(连接导向)的、可靠的、基于字节流的传输层通信协议,会存在黏包问题。

TCP服务端
  1. 监听端口
  2. 接收客户端请求建立连接
  3. 创建goroutine处理连接。
func main() {
	listen, err := net.Listen("tcp", ":8080")
	if err != nil {
		fmt.Printf("listen failed error:%v\n", err)
	}
	defer func() { _ = listen.Close() }()
	for {
		conn, err := listen.Accept()
		if err != nil {
			fmt.Printf("accept failed error:%v\n", err)
			continue
		}
		go process(conn)
	}
}

func process(c net.Conn) {
	defer func() { _ = c.Close() }()

	reader := bufio.NewReader(c)
	// 128长度为1条消息
	var buf [128]byte

	for i := 1; ; i++ {
		n, err := reader.Read(buf[:])
		if err != nil {
			if err == io.EOF {
				fmt.Println("client 断开连接")
				return
			}
			fmt.Printf("read from client error:%v\n", err)
			return
		}
		fmt.Printf("server接收到数据,NO:%d\n", i)
		fmt.Printf("Server 接收数据:%s\n", string(buf[:n]))
		_, _ = c.Write([]byte("接收数据成功"))
	}

}
TCP客户端
  1. 建立与服务端的连接
  2. 进行数据收发
  3. 关闭连接
func main() {
	conn, err := net.Dial("tcp", "127.0.0.1:8080")
	if err != nil {
		fmt.Printf("client Dial error :%v", err)
		return
	}
	defer func() { _ = conn.Close() }()

	ir := bufio.NewReader(os.Stdin)
	for {
		input, _ := ir.ReadString('\n')
		in := strings.TrimSpace(input)
		if len(in) == 0 {
			continue
		}
		if strings.ToUpper(in) == "Q" {
			fmt.Println("退出client")
			return
		}

		_, err := conn.Write([]byte(in))
		if err != nil {
			fmt.Printf("client Write error :%v", err)
			return
		}
		// 接收回复的消息
		buf := make([]byte, 128)
		n, err := conn.Read(buf)
		if err != nil {
			fmt.Printf("client Read error :%v", err)
		}

		fmt.Printf("client Read Response : %s", string(buf[:n]))
	}

}

UDP

UDP协议是用户数据报协议,是一种无连接的传输层协议,不需要建立连接就能直接进行数据发送和接收,属于不可靠的、没有时序的通信,但是UDP协议的实时性比较好,通常用于视频直播相关领域。

UDP服务端
func main() {
	conn, err := net.ListenUDP("udp", &net.UDPAddr{
		IP:   net.IPv4(0, 0, 0, 0),
		Port: 8080,
	})
	if err != nil {
		fmt.Printf("udp listen error: %v\n", err)
	}

	defer func() { _ = conn.Close() }()

	for {
		var data [1024]byte
		n, addr, err := conn.ReadFromUDP(data[:])
		if err != nil {
			fmt.Printf("udp read error: %v\n", err)
			continue
		}

		fmt.Printf("udp read success. addr:%v data:%s\n", addr, data[:n])

		_, err = conn.WriteToUDP([]byte("server 成功接收到数据"), addr)
		if err != nil {
			fmt.Printf("udp send to client error: %v\n", err)
			continue
		}

	}
}
UDP客户端
func main() {
	conn, err := net.DialUDP("udp", nil, &net.UDPAddr{
		IP:   net.IPv4(0, 0, 0, 0),
		Port: 8080,
	})
	if err != nil {
		fmt.Printf("udp 连接服务端失败 error: %v\n", err)
	}
	defer func() { _ = conn.Close() }()

	_, err = conn.Write([]byte("舒克舒克 我是贝塔"))
	if err != nil {
		fmt.Printf("发送数据失败:%v", err)
		return
	}

	var buf = make([]byte, 1024)
	n, addr, err := conn.ReadFromUDP(buf)
	if err != nil {
		fmt.Printf("udp client read error: %v", err)
		return
	}
	fmt.Printf("client 收到回话  addr:%v msg:%s", addr, string(buf[:n]))
}

HTTP

HTTP协议通常承载于TCP协议之上。

HTTP服务端
func main() {
	http.HandleFunc("/http", httpHandler)
	err := http.ListenAndServe(":8080", handler{})
	if err != nil {
		fmt.Printf("监听端口失败,error:%v", err)
	}
}

func httpHandler(res http.ResponseWriter, req *http.Request) {
	if req.URL.Path == "/favicon.ico" {
		return
	}
	fmt.Printf("%s 连接成功\n", req.RemoteAddr)
	fmt.Printf("请求方式:%s\n", req.Method)
	fmt.Printf("url:%s\n", req.URL.Path)
	fmt.Printf("header:%v\n", req.Header)
	fmt.Printf("body:%v\n", req.Body)
	b, _ := ioutil.ReadAll(req.Body)
	fmt.Printf("body:%s\n", string(b))
	fmt.Printf("request parameters:%v\n", req.URL.Query())
	req.Body = ioutil.NopCloser(bytes.NewBuffer(b))
	_ = req.ParseForm()
	fmt.Printf("request form parameter:%v\n", req.PostForm)

	res.WriteHeader(http.StatusOK)
	_, _ = res.Write([]byte("请求成功!"))
}

type handler struct {
}

func (h handler) ServeHTTP(res http.ResponseWriter, req *http.Request) {
	httpHandler(res, req)
}
HTTP客户端
func main() {
	get()
	//post()
}

func post() {
	u := struct {
		Name    string
		Comment string
	}{
		"贝塔",
		"开坦克的贝塔",
	}
	json, err := json.Marshal(u)
	//body = bytes.NewReader([]byte(`{"name": "Jack", "sex": "male", "age": 18}`))
	body := bytes.NewReader(json)

	res, err := http.Post("http://127.0.0.1:8080/http", "application/x-www-form-urlencoded", body)
	if err != nil {
		panic(err)
	}
	defer func() { _ = res.Body.Close() }()

	result, _ := ioutil.ReadAll(res.Body)
	fmt.Printf("收到server的响应 : %s\n", string(result))
}

func get() {
	req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8080/http", nil)
	if err != nil {
		panic(err)
	}
	// url 参数
	params := make(url.Values)
	params.Add("name", "贝塔")
	params.Add("age", "18")
	req.URL.RawQuery = params.Encode()
	// body json
	u := struct {
		Name string
		Age  int
		Sex  string
	}{
		"舒克",
		19,
		"男",
	}
	json, _ := json.Marshal(u)
	req.Body = ioutil.NopCloser(bytes.NewReader(json))
	// 发送请求
	res, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	defer func() { _ = res.Body.Close() }()

	buf, _ := ioutil.ReadAll(res.Body)
	fmt.Printf("收到server的响应 : %s\n", string(buf))

}

WebSocket

WebSocket是一种在单个TCP连接上进行全双工通信的协议,允许服务端主动向客户端推送数据。

go get -u -v github.com/gorilla/websocket

go get -u -v github.com/gorilla/mux

server.go
import (
	"fmt"
	"github.com/gorilla/mux"
	"net/http"
)

func main() {
	router := mux.NewRouter()
	go h.run()
	router.HandleFunc("/ws", myws)
	err := http.ListenAndServe(":8080", router)
	if err != nil {
		fmt.Printf("http ListenAndServe err:%v", err)
	}
}
hub.go
import "encoding/json"

type hub struct {
	c map[*connection]bool
	b chan []byte
	r chan *connection
	u chan *connection
}

var h = hub{make(map[*connection]bool), make(chan []byte), make(chan *connection), make(chan *connection)}

func (h *hub) run() {
	for {
		select {
		case c := <-h.r:
			h.c[c] = true
			c.data.Ip = c.ws.RemoteAddr().String()
			c.data.Type = "handshake"
			c.data.UserList = userList
			json, _ := json.Marshal(c.data)
			c.sc <- json
		case c := <-h.u:
			if _, v := h.c[c]; v {
				delete(h.c, c)
				close(c.sc)
			}
		case data := <-h.b:
			for c := range h.c {
				select {
				case c.sc <- data:
				default:
					delete(h.c, c)
					close(c.sc)
				}
			}
		}
	}
}
connection.go
import (
	"encoding/json"
	"fmt"
	"github.com/gorilla/websocket"
	"net/http"
)

type connection struct {
	ws   *websocket.Conn
	sc   chan []byte
	data *Data
}

var wu = &websocket.Upgrader{
	ReadBufferSize:  512,
	WriteBufferSize: 512,
	CheckOrigin:     func(r *http.Request) bool { return true },
}

var userList = []string{}

func myws(res http.ResponseWriter, req *http.Request) {
	ws, err := wu.Upgrade(res, req, nil)
	if err != nil {
		return
	}

	c := &connection{ws, make(chan []byte, 256), new(Data)}
	h.r <- c
	go c.writer()
	c.reader()
	defer func() {
		c.data.Type = "logout"
		userList = del(userList, c.data.User)
		c.data.UserList = userList
		c.data.Content = c.data.User
		dataB, _ := json.Marshal(c.data)
		h.b <- dataB
		h.u <- c
	}()
}

func (c *connection) writer() {
	defer func() { _ = c.ws.Close() }()

	for msg := range c.sc {
		err := c.ws.WriteMessage(websocket.TextMessage, msg)
		if err != nil {
			return
		}
	}
}

func (c *connection) reader() {
	for {
		_, msg, err := c.ws.ReadMessage()
		if err != nil {
			h.r <- c
			break
		}
		_ = json.Unmarshal(msg, c.data)
		switch c.data.Type {
		case "login":
			c.data.User = c.data.Content
			c.data.From = c.data.User
			userList = append(userList, c.data.User)
			c.data.UserList = userList
			json, _ := json.Marshal(c.data)
			h.b <- json
		case "user":
			c.data.Type = "user"
			json, _ := json.Marshal(c.data)
			h.b <- json
		case "logout":
			c.data.Type = "logout"
			userList = del(userList, c.data.User)
			json, _ := json.Marshal(c.data)
			h.b <- json
			h.r <- c
		default:
			fmt.Println("Unknown type:", c.data.Type)
		}
	}
}

func del(slice []string, user string) []string {
	cnt := len(slice)
	if cnt == 0 {
		return slice
	}

	var nSlice = slice
	for i := range slice {
		if slice[i] != user {
			continue
		}

		if cnt == 1 {
			nSlice = []string{}
		} else if i == cnt-1 {
			nSlice = slice[:cnt-1]
		} else {
			nSlice = append(slice[:i], slice[i+1:]...)
		}
		break
	}
	fmt.Println(nSlice)
	return nSlice
}
data.go
type Data struct {
	Ip       string   `json:"ip"`
	User     string   `json:"user"`
	From     string   `json:"from"`
	Type     string   `json:"type"`
	Content  string   `json:"content"`
	UserList []string `json:"userList"`
}
index.html
<!DOCTYPE html>
<html>
<head>
    <title></title>
    <meta http-equiv="content-type" content="text/html;charset=utf-8">
    <style>
        p {
            text-align: left;
            padding-left: 20px;
        }
    </style>
</head>
<body>
<div style="width: 800px;height: 600px;margin: 30px auto;text-align: center">
    <h1>演示聊天室</h1>
    <div style="width: 800px;border: 1px solid gray;height: 300px;">
        <div style="width: 200px;height: 300px;float: left;text-align: left;">
            <p><span>当前在线:</span><span id="user_num">0</span></p>
            <div id="user_list" style="overflow: auto;">
            </div>
        </div>
        <div id="msg_list" style="width: 598px;border:  1px solid gray; height: 300px;overflow: scroll;float: left;">
        </div>
    </div>
    <br>
    <textarea id="msg_box" rows="6" cols="50" onkeydown="confirm(event)"></textarea><br>
    <input type="button" value="发送" onclick="send()">
    <input type="button" value="退出" onclick="quit()">
</div>
</body>
</html>
<script type="text/javascript">
    var uname = prompt('请输入用户名', 'user' + uuid(8, 16));
    var ws = new WebSocket("ws://127.0.0.1:8080/ws");
    ws.onopen = function () {
        var data = "系统消息:建立连接成功";
        listMsg(data);
    };
    ws.onmessage = function (e) {
        var msg = JSON.parse(e.data);
        var sender, user_name, name_list, change_type;
        switch (msg.type) {
            case 'system':
                sender = '系统消息: ';
                break;
            case 'user':
                sender = msg.from + ': ';
                break;
            case 'handshake':
                var user_info = {'type': 'login', 'content': uname};
                sendMsg(user_info);
                return;
            case 'login':
            case 'logout':
                user_name = msg.content;
                name_list = msg.userList;
                change_type = msg.type;
                dealUser(user_name, change_type, name_list);
                return;
        }
        var data = sender + msg.content;
        listMsg(data);
    };
    ws.onerror = function () {
        var data = "系统消息 : 出错了,请退出重试.";
        listMsg(data);
    };
    function confirm(event) {
        var key_num = event.keyCode;
        if (13 == key_num) {
            send();
        } else {
            return false;
        }
    }
    function send() {
        var msg_box = document.getElementById("msg_box");
        var content = msg_box.value;
        var reg = new RegExp("\r\n", "g");
        content = content.replace(reg, "");
        var msg = {'content': content.trim(), 'type': 'user'};
        sendMsg(msg);
        msg_box.value = '';
    }
    function quit(){

    }
    function listMsg(data) {
        var msg_list = document.getElementById("msg_list");
        var msg = document.createElement("p");
        msg.innerHTML = data;
        msg_list.appendChild(msg);
        msg_list.scrollTop = msg_list.scrollHeight;
    }
    function dealUser(user_name, type, name_list) {
        var user_list = document.getElementById("user_list");
        var user_num = document.getElementById("user_num");
        while(user_list.hasChildNodes()) {
            user_list.removeChild(user_list.firstChild);
        }
        for (var index in name_list) {
            var user = document.createElement("p");
            user.innerHTML = name_list[index];
            user_list.appendChild(user);
        }
        user_num.innerHTML = name_list.length;
        user_list.scrollTop = user_list.scrollHeight;
        var change = type == 'login' ? '上线' : '下线';
        var data = '系统消息: ' + user_name + ' 已' + change;
        listMsg(data);
    }
    function sendMsg(msg) {
        var data = JSON.stringify(msg);
        ws.send(data);
    }
    function uuid(len, radix) {
        var chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split('');
        var uuid = [], i;
        radix = radix || chars.length;
        if (len) {
            for (i = 0; i < len; i++) uuid[i] = chars[0 | Math.random() * radix];
        } else {
            var r;
            uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-';
            uuid[14] = '4';
            for (i = 0; i < 36; i++) {
                if (!uuid[i]) {
                    r = 0 | Math.random() * 16;
                    uuid[i] = chars[(i == 19) ? (r & 0x3) | 0x8 : r];
                }
            }
        }
        return uuid.join('');
    }
</script>

MySQL

建表语句

CREATE DATABASE if NOT EXISTS go;
USE go;
DROP TABLE if EXISTS student;
CREATE TABLE student(
	s_id INT(11) NOT NULL AUTO_INCREMENT,
	s_name VARCHAR(20) DEFAULT '',
	s_sex TINYINT UNSIGNED DEFAULT 0,
	s_email VARCHAR(50) DEFAULT '',
	PRIMARY KEY (s_id)
)ENGINE=INNODB AUTO_INCREMENT=1 CHARSET=UTF8MB4;

引入MySQL驱动 import _ “github.com/go-sql-driver/mysql”

import (
	"database/sql"
	"encoding/json"
	"errors"
	_ "github.com/go-sql-driver/mysql"
	"log"
	"time"
)

var DB *sql.DB

type Student struct {
	Id    int    `db:"s_id"`
	Name  string `db:"s_name"`
	Sex   uint   `db:"s_sex"`
	Email string `db:"s_email"`
}

func init() {
	db, err := sql.Open("mysql", "root:123456@tcp(127.0.0.1:3306)/go?charset=utf8")
	if err != nil {
		log.Println("数据库连接失败")
		panic(err)
	}
	// 最大空闲连接
	db.SetMaxIdleConns(10)
	// 最大连接
	db.SetMaxOpenConns(100)
	// 连接最大存活时间
	db.SetConnMaxLifetime(time.Minute * 5)
	// 空闲连接最大存活时间
	db.SetConnMaxIdleTime(time.Minute * 1)
	err = db.Ping()
	if err != nil {
		_ = db.Close()
		log.Println("数据库ping失败")
		panic(err)
	}
	DB = db
}

func main() {
	defer func() { _ = DB.Close() }()
    // 新增
	id := add("贝塔", 1, "beta@163.com")
    // 查询
	_, _ = query(id)
    // 修改
	update("舒克", id)
	_, _ = query(id)
    // 删除
	delete(id)
	_, _ = query(id)
    // 事务
	txInsert("舒克1", 0, "123123@qq.com")
}

func add(name string, sex uint, email string) int {
	ret, err := DB.Exec("insert into student (s_name,s_sex,s_email) values (?,?,?)", name, sex, email)
	if err != nil {
		log.Printf("插入失败,err:%v\n", err)
		return 0
	}
	id, _ := ret.LastInsertId()
	log.Printf("插入成功,id:%d\n", id)

	return int(id)
}

func query(id int) (*Student, error) {
	rows, err := DB.Query("select * from student where s_id = ? limit 1", id)
	if err != nil {
		log.Printf("查询失败,error:%v\n", errors.New(err.Error()))
		return nil, err
	}

	s := new(Student)
	for rows.Next() {
		_ = rows.Scan(&s.Id, &s.Name, &s.Sex, &s.Email)
	}

	json, _ := json.Marshal(s)
	log.Printf("查询成功,%v\n", string(json))

	return s, nil
}

func update(name string, id int) {
	ret, err := DB.Exec("update student set s_name = ? where s_id = ?", name, id)
	if err != nil {
		log.Printf("更新失败,%v\n", err)
	}
	n, _ := ret.RowsAffected()
	log.Printf("更新成功,影响行数:%d\n", n)
}

func delete(id int) {
	ret, err := DB.Exec("delete from student where s_id = ?", id)
	if err != nil {
		log.Printf("删除失败,%v\n", err)
	}

	n, _ := ret.RowsAffected()
	log.Printf("删除成功,删除行数:%d\n", n)
}

func txInsert(name string, sex uint, email string) (int, error) {
	tx, err := DB.Begin()
	if err != nil {
		log.Printf("开始事务失败,%v\n", err)
		return 0, errors.New(err.Error())
	}

	ret, err := tx.Exec("insert into student (s_name,s_sex,s_email) values (?,?,?)", name, sex, email)
	if err != nil {
		log.Printf("插入失败,%v\n", err)
		return 0, errors.New(err.Error())
	}
	if name == "舒克" {
		_ = tx.Rollback()
		log.Printf("遇见舒克,回滚\n")
		return 0, errors.New("rollBack")
	} else {
		id, _ := ret.LastInsertId()
		tx.Commit()
		log.Printf("插入成功:id:%d\n", id)
		return int(id), nil
	}

}

Redis

https://github.com/redis/go-redis

go get github.com/redis/go-redis/v9

var ctx = context.Background()
var client *redis.Client

func init() {
	rc := redis.NewClient(&redis.Options{
		Addr:     "localhost:6379",
		Password: "",
		DB:       0,
	})

	client = rc
}

func main() {
	err := client.Set(ctx, "key1", "value1", 0).Err()
	if err != nil {
		panic(err)
	}

	r1, err := client.Get(ctx, "key1").Result()
	if err != nil {
		panic(err)
	}
	log.Printf("redis get key1:%s", r1)

	r2, err := client.Get(ctx, "key2").Result()
	if err == redis.Nil {
		log.Printf("redis get key2 does not exists")
	} else if err != nil {
		panic(err)
	} else {
		log.Printf("redis get key2:%s", r2)
	}
}

泛型

type Number interface {
	int64 | float64
}

func sumIntsOrFLoats[K comparable, V Number](m map[K]V) V {
	var sum V
	for _, v := range m {
		sum += v
	}
	return sum
}

func main() {
	m1 := map[string]int64{
		"key1": 5,
		"key2": 16,
	}

	m2 := map[string]float64{
		"key1": 5.0,
		"key2": 16.0,
	}

	fmt.Printf("%d\n", sumIntsOrFLoats(m1))
	fmt.Printf("%f\n", sumIntsOrFLoats(m2))
}
  • 33
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
学习 Go(也称为 Golang语言入门到精通可以分为以下几个步骤: 1. **安装和环境设置**: - 官方下载 Go 的安装包并配置好 GOPATH(Go 工作路径),用于存放源码、依赖等。 2. **基础语法**: - 学习基本的数据类型(如 int, float, string 等)、变量声明、常量定义。 - 掌握流程控制结构(if-else, for, while, switch)和函数的定义与调用。 3. **模块管理**: - 使用 `go mod` 命令来管理和导入外部库(模块),了解如何编写和使用 `import` 和 `package` 关键字。 4. **并发编程**: - Go 强调并发,理解 Goroutines(轻量级线程)和 Channels(管道通信机制)的概念。 - 学习互斥锁(sync.Mutex)和通道选择器(select)等同步原语。 5. **标准库的探索**: - 熟悉标准库提供的常用功能,如 fmt (格式化)、io (输入/输出)、net (网络)、os (操作系统接口) 等。 6. **HTTP服务器与客户端**: - 学会使用 net/http 包创建简单的 HTTP 服务端和客户端。 7. **Web框架**: - 如果对 Web 开发感兴趣,可以尝试 Gin 或 Beego 这样的轻量级框架。 8. **错误处理与日志记录**: - 学习如何优雅地处理和捕获运行时错误,以及使用 logrus 或 zap 进行日志记录。 9. **项目实战**: - 通过实际项目练习,比如搭建简单的 RESTful API、数据处理工具或游戏后端。 10. **进阶主题**: - 对于高级开发者,可研究 goroutine 性能优化、内存管理(垃圾回收机制)、反射、接口和组合等概念。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值