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

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

发布了85 篇原创文章 · 获赞 55 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/shelgi/article/details/103929345