第五章 Go语言并发
一、goroute
理论
进程:进程是程序在操作系统中的一次执行过程,系统进行资源分配和调度的一个独立单位
线程:线程是进程的一个执行实体,是CPU调度和分配的基本单位,是比进程跟小的能独立运行的基本单位
进程与线程的关系:一个进程一颗创建和撤销多个线程,同一个进程中的多个线程之间可以并发执行
协程:独立的栈空间,共享堆空间,调度由用户自己控制,本质上优点类似用户级线程,这些用户线程的调用也是自己实现的
协程和线程的关系:一个线程可以跑多个协程,协程是轻量级线程
并发:同一时间段内执行多个操作
并行:同一时刻执行多个操作
Gorouteine多线程:线程是有操作系统进行管理,处于内核态,线程之间进行切换需要发生用户态到内核态的切换
当系统中运行大量线程,系统会变得非常慢,用户态的线程支持大量线程的创建,也叫协程或者goroutine
Goroutine使用介绍
//创建goroutine func hello() { fmt.Println("hello goroutine") } func main() { go hello() fmt.Prinln("main thread terminate") time.Sleep(time.Second) //goroutine会随着主线程的结束而结束,需要等待1s } //启动多个goroutine func hello(i int) { fmnt.Println("hello goroutine",i) } func test() { for i := 0<10;i++{ go hello(i) } } func main() { test() time.Sleep(time.Second) } //多核控制 var i int func calc() {for {i++)} func main() { cpu := runtime.NumCPU() //显示当前CPU有多少核 fmt.Prinln("cpu",cpu) runtime.GOMAXPROCS(1) //只允许调用一个cpu内核 for i := 0;i<10;i++ { go calc() } time.Sleep(time.Hour) }
Goroutine原理解析
概念:一个操作系统线程对应用户态多个goroutine,可同时使用多个操作系统线程,操作系统线程对goroutine是多对多关系
调度:当操作系统线程需要执行时,开始逐个执行goroutine
蓝色goroutine执行结束后再执行灰色,当前有两个线程在执行
系统调用的处理
二、goroutine之间的通信
全局变量和锁同步
import ( "fmt" "time" "sync" ) var m make(map[int]uint64) type tesk struct { n int } func calc(t *task) { var sum uint64 sum = 1 for i := 1;i<t.n;i++ { sum *= uint64(i) } lock.Lock() m[t.n] = sum lockUnlock() } func main() { for i := 0;i<100;i++ { t := &task{n:i} go calc(t) } time.Sleep(10 * time.Second) lock.Lock() for k,v := range m { fmt.Printf("%d!=%v\n",k,v) } lock.Unlock() }
channel(队列管道)
介绍:本质上是一个队列/容器,先进先出,类似unix中的管道pipe
定义:需要指定勇气中元素的类型 var 变量名 chan 数据类型
var test chan int var test chan string var test chan map[string]string var test chan stcurt var test chan *stcurt //定义一个地址,只能传入地址
入队:a <- 100
出队:data := <-a
//test1 func main() { var c chan int fmt.Printf("c =%v\n",c) c = make(chan int,10) //如果不分配空间,则入队一直阻塞,运行报错 fmt.Printf("c = %v\n",c) c <- 100 //<-c不给变量存储则为丢弃这个元素 data := <-c fmt.Println("data",data) } //test2 type student struct { name string } func main() { var stuChan chan student stuChan = make(chan *Student,10) stu := student{name:"stu01"} stuChan <- stu } //test3 func produce(c chan int) { c <- 1000 fmt.Println("produce finished") } func consume (c chan int) { data := <-c fmt.Println("data",data) } func main() { var c chan int fmt.Printf("c = %v\n",c) c = make(chan int) go produce(c) go consume(c) time.Sleep(time.Second * 3) } //不带缓冲区的队列,有出队的时候才能入队,否则入队失败
channel练习
//chan和goroutine同步 func hello(c chan bool){ fmt.Println("hello goroutine") c <- true } func main(){ var exitChan chan bool exitChan = make(chan bool) go hello(exitChan) fmt.Println("main thread terminate") <-exitChan } //单向chan func sendData(sendch chan<- int) { chan<- 表示之能入队 sendch <- 10 } func readData(sendch <-chan int) { <- chan 表示只能出队 data := <-sendch fmt.Println("data:", data) } func main() { chnl := make(chan int) go sendData(chnl) readData(chnl) } //chan关闭 func producer(chnl chan int) { for i := 0; i < 10; i++ { chnl <- i } close(chnl) //关闭chan } func main() { ch := make(chan int) go producer(ch) for { v, ok := <-ch if ok == false { //判断chan是否关闭,如果无法判断管道是否关闭会一直取出默认值0保持死循环 fmt.Println("chan is closed") break } fmt.Println("Received", v, ok) } } //for range func produer(chnl chan int) { for i := 0; i < 10; i++ { chnl <- i } close(chnl) } func main() { ch := make(chan int) go produer(ch) for v := range ch { //chan关闭无数据后自动运行结束 fmt.Println("receive:", v) } } //带缓冲区的chanel func write( ch chan int) { for i:= 0;i<5;i++ { ch <- i fmt.Println("successfully wrote",i,"to ch") } close(ch) } func main() { ch := make(chan int,2) go write(ch) time.Sleep(2 * time.Second) for v := range ch { fmt.Println("read value",v,"from ch") time.Sleep(2 *time.Second) } } //channel的长度和容量 func main() { ch := make(chan string,3) ch <- "naveen" ch <- "paul" fmt.Println("capacity is",cap(ch)) fmt.Println("length is",len(ch)) fmt.Println("read value",<-ch) fmt.Println("new length is",len(ch)) } //waitgroup等待一组goroutine结束,使用不带缓冲区的channel实现 func process(i int,ch chan bool) { fmt.Println("started Goroutine",i) time.Sleep(2 * time.Second) fmt.Printf("Goroutine %d ended\n",i) ch <- true } func main() { no := 3 exitChan := make(chan bool,no) for i := 0;i<no;i++ { go process(i,exitChan) } for i:= 0;i<no;i++ { <exitChan } fmt.Println("All goroutines finshed executing") } //使用sync-WaitGroup实现 func process(i int,wg *sync.WaitGroup) { fmt.Println("started Goroutine",i) time.Sleep(2 * time.Second) fmt.Printf("Goroutine %d ended\n",i) wg.Done() //wg-1 } func main() { no := 3 var wq sync.WaitGroup //定义一个wgh,默认为0 for i := 0; i<no; i++ { wg.Add(1) wg+1 go process(i,&wg) } wg.Wait() //当wg为0时才会向下执行 fmt.Println("All goroutines finished executing“) }”
三、select和线程之间的安全
select语义介绍
定义:多channel操作,同时监听一个或多个channel,直到其中一个channel ready,如果其中合格多个chgannel同时ready,随机选择一个进行操作,语法和weitch case类似,只获取一个数值就结束
func server1(ch chan string) { time.Sleep(time.Second * 3) ch <- "response from server1" } func server2(ch chan string) { time.Sleep(time.Second) ch <-"response from server2") } func main() { output1 := make(chan string) output2 := make(chan stirng) go server1(output1) go server2(output2) //s1 := <-output1 //等待3s //fmt.Println("s1:",s1) //s2 := <-output2 //等待1s后等待s1结束再执行 //fmt.Println("s2",s2) select { //最终只取一个channel的结果 case s1 := <-output1: fmt.Println("s1:",s1) case s2 := <-output2: fmt.Prinln("s2:",s2) } }
default分支:放case分支的channel都没有ready的话,执行default,用来判断channel是否满了或者为空
//test1 func server1(ch chan string) { time.Sleep(time.Second * 3) ch <- "resdponse from server1" } func server2(ch chan string) { time.Sleep(time.Second) ch <- "response from server2" } func main() { output1 := make(chan string) output2 := make(chan string) go server1(output1) go server2(output2) select { //最终只取一个channel的结果 case s1 := <- output1: fmt.Println("s1;",s1) case s2 := <-output2: fmt.Println("s2:",s2) default: fmt.Println("run default") //因为s1和s2需要等待,default瞬间执行 } } //test2 func write(ch chan string) { for { select { case ch <- "hello": fmt.,Println("write succ") default: fmt.Println("channel is full") } time.Sleep(time.Millisecond * 500) } } func main() { output1 := make(chan string,10) go write(output1) for s := range.output1 { fmt.Println("recv:"s) time.Sleep(time.Second) } }
线程安全介绍
定义:多个goroutine同时操作一个资源,这个资源又叫做临界区
var x int var wg sync WaitGrroup func add() { for i := 0;i<500;i++ { x = x + 1 } wg.Done() } func main() { wg.Add(2) go add() go add() wg.Wait() fmt.Println("x:"x) //结果不一定等于1000 }
互斥锁介绍
定义:同时有且只有一个线程进入临界区,其他的线程则在等待锁,当互斥锁释放之后,等待锁的线程才可以获取锁进入临界区,多个线程同时等待同一个锁,唤醒的策略是随机的
var x int var wg synv.WaitGroup var mutex sync.Mutex //生明锁 func add() { for i :=0;i<500;i++ { mutex.Lock() //加锁 x = x + 1 mutex.Unlock() //释放 } wg.Done() } func main() { wg.Add(2) go add() go add() wg.Wait() fmt.Println("x:",x) }
读写锁介绍
定义:读多写少的场景,分为读锁和写锁,当一个goroutine获取写锁之后,其他goroutine获取写锁或读锁都需要等待
当一个goroutine获取读锁之后,其他的goroutine获取写锁都会等待,但其他goroutine获取读锁时,
var rwlock sync.RWMutex var wg sync.WaitGroup var x int func write() { rwlock.Lock() fmt.Println("write lock") x = x + 1 time.Sleep(time.Second * 2) fmt.Println("write unlock") rwlock.Unlock() wg.Done() } func read(i int) { rwlock.RLock() fmt.Printf("goroutine:%d x=%d\n",i,x) time.Sleep(time.Second) rwlock.RUnlock() wg.Done() } func main() { wg.Add(1) go write() for i:=0;i<10;i++ { wg.Add(1) go read(i) } wg.Wait() } /* goroutine:9 x=0 //当channel中没有数据时,返回默认值 goroutine:0 x=0 write lock write unlock goroutine:5 x=1 goroutine:3 x=1 goroutine:8 x=1 goroutine:2 x=1 goroutine:6 x=1 goroutine:1 x=1 goroutine:7 x=1 goroutine:4 x=1 */
读写锁和互斥锁的对比
//读写锁 var rwlok sync.RWMutex var wg sync.WaitGroup var x int func write() { for i :=0;i<100;i++ { rwlock.Lock() x = x+1 time.Sleep(time.Millisecond * 10) rwlock.Unlock() } wg.Done() } func read(i int) { for i:=0;i<100;i++ { rwlock.RLock() time.Sleep(time.Millsecond) rwlock.RUnlock() } wg.Done() } func main() { start := time.Now().UnixNano() wg.Add(1) go write() for i:=0;i<10;i++ { wg.Add(1) go read(i) } wg.Wait() end := time.Now().UnixNano() cost := (end-start)/1000/1000 fmt.Println("cost:",cost,"ms") //cost:1173ms } //互斥锁 var wg synv.WaitGroup var mutex synv.Mutex var x int func write() { for i:=0;i<100;i++ { mutex.Lock() x = x+1 time.Sleep(time.Millisecond * 10) mutex.Unlock() } wg.Done() } func read(i int) { for i:=0;i<100;i++ { mutex.Lock() time.Sleep(time.Millisecond) mutex.Unlock() } wg.Done() } func main() { start := time.Now().UnixNano() wg.Add(1) go write() for i:= 0;i<10;i++ { wg.Add(1) go read(i) } wg.Wait() end := time.Now().UnixName() cost := (end-start)/1000/1000 fmt.,Println("cost:",cost,"ms") //cost:2410ms }
原子操作介绍
定义:加锁代价比较耗时需要上下文切换,针对基本数据类型,可以使用原子操作保证线程安全,原子操作在用户态就可以完成,因此性能比互斥锁要高
内容:
//加减操作 func AddInt32(addr *int32,delta int32)(new int32) func AddInt64(addr *int64,delta int64)(new int64) func AddUint32(addr *uint32,delta uint32)(new uint32) func AddUint64(addr *int64,delta uint64)(new uint64) func AddUintptr(addr *uintptr,delta uintptr)(new uintptr) //比较并交换 func CompareAndSwapInt32(addr *int32,old,new int32)(swapped bool) func CompareAndSwapInt64(addr *int64,old,new int64)(swapped bool) func CompareAndSwapPointer(addr *unsafe,Pointer,old,new unsafe.Pointer)(swapped bool) func CompareAndSwapUint32(addr *uint32,old,new uint32)(swapped bool) func CompareAndSwapUint64(addr *uint64,old,new uint64)(swapped bool) func CompareAndSwapUintptr(addr *uintptr,old,new uintptr)(swapped bool) //读取操作 func LoadInt32(addr *int32)(val int32) func LoadInt64(addr *int64)(val int64) func LoadIntPointer(addr *unsafe.Pointer)(val unsafe.Pointer) func LoadUint32(addr *uint32)(val uint32) func LoadUint64(addr *uint64)(val uint64) func LoadUintptr(addr *uintptr)(val uintptr) //写入操作 func StoreInt32(addr *int32,val int32) func StoreInt64(addr *int64,val int64) func StorePointer(addr *unsafe.Pointer,val unsafe.Pointer) func StoreUint32(addr *uint32,val uint32) func StoreUint64(addr *uint64,val uint64) func StoreUintptr(addr *uintptr,val uintptr) //交换操作 func SwapInt32(addr *int32,new int32)(old int32) func SwapInt64(addr *int64,new int64)(old int64) func SwapIntPointer(addr *unsafe.Pointer,new unsafe.Pointer)(old unsafe.Pointer) func SwapUint32(addr *uint32,new uint32)(old uint32) func SwapUint64(addr *uint64,new uint64)(old uint64) func SwapUintptr(addr *uintptr,new uintptr)(old uintptr)
原子性操作和互斥锁的对比
//test' var x int32 var wg sync.WaitGroup func add() { for i:= 0;i<500;i++ { atomic.AddInt32(&x,1) } wg.Done() } func main() { wg.Add(2) go add() go add() wg.Wait() fmt.Println("x:",x) } //区别 var x int32 var wg stnc.WaitGroup var mutex sync.Mutex func addMutex() { for i:=0;i<500;i++ { mutex.Lock() x = x+1 mutex.Unlock() } } func add() { for i := 0;i<500;i++ { atomic.AddInt32(&x,1) } wg.Done() } func main() { start := time.Now().UnixNano() for i:= 0;i<500;i++ { wg.Add(1) go add() go addMutex() } wg.Wait() end := time.Now().UnixNano() cost := (end - start)/1000/1000 fmt.Println("x:"x,"cost:",cost,"ms") } //当线程开的很多时,原子性操作比互斥锁效率高
四、网络编程
TCP/IP协议介绍
定义:提供已连接英特网的计算机进行通信的协议,TCP(传输控制协议)——应用程序时间通信,UDP(用户数据包协议)——应用程序之间的简单通信,IP(网络协议)——计算机之间的通信,DHCP(动态配置协议)——针对动态寻址
TCP协议:面向连接的协议,可靠传输,发送的数据保证对方能够收到,保证时序,发送的数据按发送的顺序到达,全双工模式,通过IP和端口进行计算机之间进行访问,域名和已知端口,http:80,http:443,ssl:22端口等
IP协议:用于计算机之间进行通信,是TCP/UDP协议的底层,IP是无连接的,负责把数据包路由到目的地
Go快速实现TCP服务器
服务端处理流程:监听端口,接收客户端的连接,创建goroutine,处理该连接
package main import ( "fmt" "net" ) func process(conn net.Conn) { defer conn,Close() for { var buf [128]byte n,err := conn.Read(buf[:]) if err != nil { fmt.Printf("read from conn failed,err:%v",err) break } str := string(buf[:n]) fmt.Printf("recv from lient,data:%v\n",str) } } func main() { listen,err := net.Listen("tcp","0.0.0.0:8080") if err != nil P fmt.Println("listen failed,err:",err( return } for { conn,err := listen.Accept() if err != nil { fmt.Printf("accept failed,err:%c\n",err) continue } go process(conn) } }
Go快速实现TCP客户端
客户端处理流程:建立服务端的连接,进行数据收发,关闭连接
//客户端 import ( "bufio" "fmt" "net" "os" "strings" ) func main() { conn,err := net.Dial("tcp","0.0.0.0:8080") if err := nil { fmt.Println("dial failed,err:%v\n",err) return } reader := bufio.NewReader(os.Stdin) for { data,err := reader.ReadString("\n") if err != nil { fmt.Printf("read from console failed,err:%v\n",err) break } data = strings.TrimSpace(data) if data == "Q" { return } _,err != nil { if err != nil { fmt.Printf("write failed,err:%v\n",err) break } } } //访问网页 func main() { conn,err := net.Dial("tcp","www.baidu.com:80") if err != nil { fmt.Printf{"dial failed,err:%v\n",err) return } data := "GET/HTTP/1.1\r\n" data += "HOST:www.baidu.com\r\n" data += "connection:close\r\n" data += "\r\n\r\n" _,err = io.WriteString(conn,data) if err != nil { fmt.printf("write string failed,err:%v\n",err) return } var buf [1024]byte for { n,err := connRead(buf[:]) if err != nil || n == 0 { break } fmt.Println(string(buf[:])) } } //得到html源码
UDP协议介绍
定义:用户数据报协议,无连接直接进行数据发送,不可靠没有时序,实时性比较好,通常用于视频世博相关领域
//server func main() { listen,err := net.ListenUDP("udp",&net.UDPAddr{ IP: net.IPv4(0.0.0.), Port:8080, }) if err != nil { fmt.Printf("listen failed,err:%v\n",err) return } for { var data [1024]byte count,addr,err := listen.ReadFromUDP(data[:]) if err != nil { fmt.Printf("read udp failed,err:%v\n",err) continue } fmt.Printf("data:%s addr:%v count:%d\n",string(data[0:count]),addr,count) _,err = listen.WaiteToUDP([]byte("hello client"),addr) if err != nil { fmt.Printf("read udp failed,err:%v\n",err) continue } } } //client func main() { socket,err := net.DialUDP("udp4",nil,&net.UDPAddr{ IP: net.IPv4(127.0.0.1), Port:8080, }) if err != nil { fmt.Println("连接失败",err) return } defer socker.Close() senddata := []byte("hello server") _,err = socket.Write(senddata) if err != nil { fmt.Println("发送数据失败",err) return } data := make([]byte,4096) read,remoteAddr,err := socket.ReadFromUDP(data) if err != nil { fmt.Println("读取数据失败",err) return } fmt.Println(read,remoteAdd) fmt.Printf("%s\n",data) }