接着昨天的说,今天主要就是并发安全、锁以及两个简单的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的知识。