Go语言学习-进阶篇

参考文章:
Go系列文章 :https://www.cnblogs.com/wdliu/category/1233750.html
Go 语言中文网系列教程: https://studygolang.com/subject/2

三、Go语言进阶篇

3.1 Go协程与并发安全

调用函数或者方法时,在前面加上关键字 go,可以让一个新的 Go 协程并发地运行。
启动一个新的协程时,协程的调用会立即返回,如果希望运行其他 Go 协程,Go 主协程必须继续运行着。

package main

import (  
    "fmt"
    "time"
)

func hello() {  
    fmt.Println("Hello world goroutine")
}
func main() {  
    go hello()
    time.Sleep(1 * time.Second)
    fmt.Println("main function")
}

并发示例:

func main() {
	for i := 0; i < 10; i++ {
		go func(i int) {
			//time.Sleep(time.Second * 1)
			fmt.Println(i)
		}(i)
	}
	time.Sleep(time.Millisecond * 50)
}

以多个窗口并发卖票为例:

package main
 
import "fmt"
import "time"
import "math/rand"
import "runtime"
 
var total_tickets int32 = 10;
 
func sell_tickets(i int){
    for{
        if total_tickets > 0 { //如果有票就卖
            time.Sleep( time.Duration(rand.Intn(5)) * time.Millisecond)
            total_tickets-- //卖一张票
            fmt.Println("id:", i, "  ticket:", total_tickets)
        }else{
            break
        }
    }
}
 
func main() {
    runtime.GOMAXPROCS(4) //我的电脑是4核处理器,所以我设置了4
    rand.Seed(time.Now().Unix()) //生成随机种子
 
    for i := 0; i < 5; i++ { //并发5个goroutine来卖票
         go sell_tickets(i)
    }
    //等待线程执行完
    var input string
    fmt.Scanln(&input)
    fmt.Println(total_tickets, "done") //退出时打印还有多少票
}

可能的输出为:

id: 1   ticket: 9
id: 0   ticket: 8
id: 3   ticket: 7
id: 4   ticket: 6
id: 1   ticket: 5
id: 1   ticket: 4
id: 2   ticket: 3
id: 1   ticket: 2
id: 0   ticket: 1
id: 4   ticket: 0
id: 1   ticket: -1
id: 3   ticket: -2
id: 2   ticket: -3
id: 0   ticket: -4

可见,我们需要使用上锁,我们可以使用互斥量来解决这个问题。

3.2 并发安全性
互斥量

即在共享资源读写前添加互斥锁:

func sell_tickets(i int){
    for total_tickets > 0 {
        mutex.Lock()
        if total_tickets > 0 { //如果有票就卖
            time.Sleep( time.Duration(rand.Intn(5)) * time.Millisecond)
            total_tickets-- //卖一张票
            fmt.Println("id:", i, "  ticket:", total_tickets)
        }
        mutex.Unlock()
    }
}
原子操作
package main
 
import "fmt"
import "time"
import "sync/atomic"
 
func main() {
    var cnt uint32 = 0
    for i := 0; i < 10; i++ {
        go func() {
            for i:=0; i<200; i++ {
                time.Sleep(time.Millisecond)
                atomic.AddUint32(&cnt, 1)
            }
        }()
    }
    time.Sleep(time.Second)//等一秒钟等goroutine完成
    cntFinal := atomic.LoadUint32(&cnt)//取数据
    fmt.Println("cnt:", cntFinal)
}
3.3. Channel信道

通过信道来实现 Go 协程间的通信,发送与接收信道默认是阻塞的。
channel和切片有点类似,默认是nil,引用类型。
常见操作:

//创建
ch := make(chan int)

//读写
// write to channel
ch <- x
// read from channel
x <- ch

//关闭
close(ch)

示例代码:

package main

import (  
    "fmt"
)

func hello(done chan bool) {  
    fmt.Println("Hello world goroutine")
    done <- true
}
func main() {  
    done := make(chan bool)
    go hello(done)
    <-done
    fmt.Println("main function")
}

上面的chan是无缓冲的,常用来作为同步,有无缓冲的示例图对比:
在这里插入图片描述

指定channel的buffer

这里参考文章:由浅入深学channelGolang实现经典并发模型:生产者消费者
这个代码的有点类似Unix C中信号量的思想:

package main
import (
	"fmt"
	"math/rand"
	"sync"
	"time"
)
 
type Product struct {
	prod_num int //生产该产品的生产者序号
	value    int
}
 
//采用通道实现产品双向传递,缓冲区设为10
//通道的运作方式替代了伪代码中 empty 和 full 信号量的作用
//通道的另一个特性:任意时刻只能有一个协程能对 channel 中某一个item进行访问。替代了伪代码中 mutex 信号量的作用
var ch = make(chan Product, 10)
 
//终止生产者生产的信号,否则无限执行下去
var stop = false
 
func Producer(wg *sync.WaitGroup, p_num int) {
	for !stop { //不断生成,直到主线程执行到终止信号
		p := Product{prod_num: p_num, value: rand.Int()}
		ch <- p
		fmt.Printf("producer %v produce a product: %#v\n", p_num, p)
		time.Sleep(time.Duration(rand.Intn(1000)) * time.Millisecond) //延长执行时间
	}
	wg.Done()
}
 
func Consumer(wg *sync.WaitGroup, c_num int) {
        //通道里没有产品了就停止消费
	for p := range ch {
		fmt.Printf("consumer %v consume a product: %#v\n", c_num, p)
		time.Sleep(time.Duration(rand.Intn(1000)) * time.Millisecond)
	}
	wg.Done()
}
 
func main() { //主线程
	var wgp sync.WaitGroup
	var wgc sync.WaitGroup
	wgp.Add(2)
	wgc.Add(2)
	//设5个生产者、5个消费者
	for i := 0; i < 2; i++ {
		go Producer(&wgp, i)
		go Consumer(&wgc, i)
	}
	time.Sleep(time.Duration(1) * time.Second)
	stop = true
	wgp.Wait()
	close(ch)
	wgc.Wait()

}

输出:
在这里插入图片描述

3.4 定时器

Go中的timer只触发一次,设计时需注意:

package main
 
import "time"
import "fmt"
 
func main() {
 
    ticker := time.NewTicker(time.Second)
 
    go func () {
        for t := range ticker.C {
            fmt.Println(t)
        }
    }()
 
    //设置一个timer,10钞后停掉ticker
    timer := time.NewTimer(10*time.Second)
    <- timer.C
 
    ticker.Stop()
    fmt.Println("timer expired!")
}
3.5 Socket编程

coolshell上给的demo非常经典,已测试
服务器server.go

package main
 
import (
    "net"
    "fmt"
    "io"
)
 
const RECV_BUF_LEN = 1024
 
func main() {
    listener, err := net.Listen("tcp", "0.0.0.0:6666")//侦听在6666端口
    if err != nil {
        panic("error listening:"+err.Error())
    }
    fmt.Println("Starting the server")
 
    for {
        conn, err := listener.Accept() //接受连接
        if err != nil {
            panic("Error accept:"+err.Error())
        }
        fmt.Println("Accepted the Connection :", conn.RemoteAddr())
        go EchoServer(conn)
    }
}
 
func EchoServer(conn net.Conn) {
    buf := make([]byte, RECV_BUF_LEN)
    defer conn.Close()
 
    for {
        n, err := conn.Read(buf);
        switch err {
            case nil:
                conn.Write( buf[0:n] )
            case io.EOF:
                fmt.Printf("Warning: End of data: %s \n", err);
                return
            default:
                fmt.Printf("Error: Reading data : %s \n", err);
                return
        }
     }
}

客户端client.go

package main
 
import (
    "fmt"
    "time"
    "net"
)
 
const RECV_BUF_LEN = 1024
 
func main() {
    conn,err := net.Dial("tcp", "127.0.0.1:6666")
    if err != nil {
        panic(err.Error())
    }
    defer conn.Close()
 
    buf := make([]byte, RECV_BUF_LEN)
 
    for i := 0; i < 5; i++ {
        //准备要发送的字符串
        msg := fmt.Sprintf("Hello World, %03d", i)
        n, err := conn.Write([]byte(msg))
        if err != nil {
            println("Write Buffer Error:", err.Error())
            break
        }
        fmt.Println("send:", msg)
 
        //从服务器端收字符串
        n, err = conn.Read(buf)
        if err !=nil {
            println("Read Buffer Error:", err.Error())
            break
        }
        fmt.Println("recv:", string(buf[0:n]))
 
        //等一秒钟
        time.Sleep(time.Second)
    }
}

输出:

go run server.go 
Starting the server
Accepted the Connection : 127.0.0.1:10834
Warning: End of data: EOF 
Accepted the Connection : 127.0.0.1:10866
Warning: End of data: EOF 

---
go run client.go
send: Hello World, 000
recv: Hello World, 000
send: Hello World, 001
recv: Hello World, 001
3.6 json编解码
编码
type Person struct {
    Name string 
    age int 
    Hobbies []string 
}

如果是类似上述结构体,那么编码时age对应的字段是不会被编码的,究其原因:

只要是可导出成员(变量首字母大写),都可以转成json。因成员变量sex是不可导出的,故无法转成json。
如果变量打上了json标签,如Name旁边的 json:"name" ,那么转化成的json key就用该标签“name”

因此:

```go
package main


import (
    "fmt"
    "log"
    "encoding/json"
)


type Person struct {
    Name string `json:"name"`
    Age int `json:"age"`
    Hobbies []string `json:"hobbies"`
}


func  main()  {
    hobbies := []string{"book","film"}
    p := Person{
        Name : "paopao",
        Age : 22,
        Hobbies : hobbies,
    }


    fmt.Printf("%T, %+v\n",p, p)


    //转换为字节切片
    jsonByteData, err := json.Marshal(p)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("%T\n", jsonByteData)


    //转化为string
    jsonStringData := string(jsonByteData)
    fmt.Printf("%T, %s\n", jsonStringData, jsonStringData)
    
    //解码
    var newPerson Person
    err = json.Unmarshal([]byte(jsonStringData),&newPerson)                                                
    if err !=nil{                                                                        
        fmt.Println("err",err)                                                          
    }                                                                                    
    fmt.Printf("%T  ", newPerson)
    fmt.Println(newPerson)       

}

输出:
在这里插入图片描述

3.7 文件操作
3.8 Mysql操作
相关推荐
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页