接着昨天的说,今天主要就是并发安全、锁以及两个简单的web例子
1.并发安全和锁
既然涉及到安全,那就要说说为什么会不安全。昨天说了goroutine的并发,那么如果多个goroutine在没有互相同步的时候同时访问某个资源,并且进行i/o操作,那这个时候我们就叫它们处于竞争状态,简称就是竞态。下面来看个竞态的例子
package main import ( "fmt" "runtime" "sync" ) var ( count int //定义一个全局变量,之后会对它进行操作 wg1 sync.WaitGroup ) func incCounter(id int) { defer wg1.Done() for i := 0; i < 2; i++ { value := count runtime.Gosched() value++ count = value } } func main() { wg1.Add(2) go incCounter(1) go incCounter(2) wg1.Wait() fmt.Println("最后的count值为:", count) }
分析一下,明明用了两个goroutine,并且每个goroutine都会执行两次,结果不应该是4吗?为什么是2。这个时候就涉及到竞态了,也就是每个goroutine会覆盖其他goroutine的操作,所以就出现了错误。这个时候锁的重要性就体现出来了,当然我们也可以运用原子函数来消除竞态的影响,但是它只支持几种内置基本数据类型,所以锁还是最为关键的。最常用的就是互斥锁(mutex),它可以保证同一时间只有一个goroutine可以访问这个资源,再用mutex来修改上面的代码看看效果。
package main import ( "fmt" "runtime" "sync" ) var ( count int //定义一个全局变量,之后会对它进行操作 wg1 sync.WaitGroup mutex sync.Mutex ) func incCounter(id int) { defer wg1.Done() for i := 0; i < 2; i++ { mutex.Lock() value := count runtime.Gosched() value++ count = value mutex.Unlock() } } func main() { wg1.Add(2) go incCounter(1) go incCounter(2) wg1.Wait() fmt.Println("最后的count值为:", count) } 复制代码
加上一个mutex后的结果
这样就正常了,所以互斥锁可以很好的防止竞态问题。当一个读的操作远远大于写操作的时候,用单纯的互斥锁是不是就很浪费,这时候还有个读写互斥锁
package main import ( "fmt" "sync" "time" ) //读写互斥锁:读的次数远远大于写的次数 var ( x1 int64 wg1 sync.WaitGroup lock1 sync.Mutex //互斥锁 rwlock sync.RWMutex //读写互斥锁 ) func read() { //lock1.Lock() rwlock.RLock() time.Sleep(time.Millisecond) //lock1.Unlock() rwlock.RUnlock() wg1.Done() } func write() { //lock1.Lock() rwlock.Lock() x1 += 1 time.Sleep(time.Millisecond * 10) //lock1.Unlock() rwlock.Unlock() wg1.Done() } func main() { start := time.Now() for i := 0; i < 1000; i++ { wg1.Add(1) go read() } for i := 0; i < 10; i++ { wg1.Add(1) go write() } wg1.Wait() fmt.Println(time.Now().Sub(start)) } //sync.Once 只运行一次 //sync.Map 并发安全的map并且是空接口类型,Store、Load、LoadorStore、Delete、Range 复制代码
用这个例子会发现在读的次数远远大于写的次数的情况下,使用读写互斥锁的速度会比用互斥锁快很多。
2.web的例子
1.web中的"HELLO WORLD"
package main import ( "fmt" "net/http" ) func handle(write http.ResponseWriter, request *http.Request) { fmt.Fprintf(write, "hello world,%s!", request.URL.Path[1:]) } func main() { http.HandleFunc("/", handle) http.ListenAndServe(":8081", nil) 复制代码
代码很简单,就是把你设置的端口启动web服务,然后打印hello world以及端口后的网址信息
2.一个简单的服务器端与客户端的通信服务器端
package main import ( "bufio" "fmt" "net" ) //tcp server demo func process(conn net.Conn) { defer conn.Close() for { reader := bufio.NewReader(conn) var buf [128]byte n, err := reader.Read(buf[:]) if err != nil { fmt.Printf("read from conn failed,err:%v\n", err) break } recv := string(buf[:n]) fmt.Println("接收到的数据为:", recv) conn.Write([]byte("ok")) } } func main() { listen, err := net.Listen("tcp", "127.0.0.1:20000") if err != nil { fmt.Printf("listen failed,err:%v\n", err) return } for { conn, err := listen.Accept() if err != nil { fmt.Printf("accept failed,err:%v\n", err) continue } go process(conn) } } 复制代码
客户端
package main import ( "bufio" "fmt" "net" "os" "strings" ) func main() { //1.与服务端建立连接 conn, err := net.Dial("tcp", "127.0.0.1:20000") if err != nil { fmt.Printf("dial failed,err:%v\n", err) return } //2.利用连接发送接收数据 input := bufio.NewReader(os.Stdin) for { s, _ := input.ReadString('\n') s = strings.TrimSpace(s) if strings.ToUpper(s) == "Q" { return } //给服务端发消息 _, err := conn.Write([]byte(s)) if err != nil { fmt.Printf("senf failed,err:%v\n", err) return } //从服务器接受回复的消息 var buf [1024]byte n, err := conn.Read(buf[:]) if err != nil { fmt.Printf("read failed,err:%v\n", err) return } fmt.Println("收到服务端回复:", string(buf[:n])) } } 复制代码
运行结果:
其实web开发主要还是要用框架,像python的flask,django……java的ssm,ssh……所以go语言搞web的话也是要用框架的,所以最近也会去学学关于gin的知识。