当前位置 博文首页 > shelgi的博客:go语言几个最重要知识点的总结(2)

    shelgi的博客:go语言几个最重要知识点的总结(2)

    作者:[db:作者] 时间:2021-07-27 21:01

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

    cs