读书笔记之Go并发

协程goroutine

  1. 使用:go 函数名( 参数列表 ),当然也可以调用匿名函数 或 闭包。
  2. main函数会默认开启一个主goroutine【main函数结束,所有的goroutine都会结束】
  3. goroutine是由Go语言在运行时调度(直接在用户态调度),而线程是由操作系统调度!(需要转换为核心态)
  4. goroutine可以并发的,可以抢占式任务处理!
  5. Go1.5后默认执行runtime.GOMAXPROCS(runtime.NumCPU()),根据CPU数控制Go进程中的线程数!
  6. goroutine之间使用channel通信

普通通道channel

goroutine之间使用channel通信,channel就是一个队列,遵循先进先出,保证收发数据的顺序!

声明通道

普通声明

var 通道变量 chan 通道数据类型 这种声明是nil不会初始化!

初始化声明

通道实例 := make(chan 数据类型)

	/1. 整型类型的通道
	ch1 := make(chan int)
	
	/2. 空接口类型、可以存放任意格式
	ch2 := make(chan interface{})

	/3. 存放结构体指针
	type User struct {}
	ch3 := make(chan *User)

使用通道

发送chan <-

	// 空接口类型、可以存放任意格式
	ch2 := make(chan interface{})
	
	ch2 <- 2
	ch2 <- "hello"
	ch2 <- new(User)

接受<- chan

close(ch2):关闭通道;ok:返回当前通道是否被关闭!【关闭的话返回的是数据类型的0值

	data := <- ch2
	
	data,ok := <- ch2

忽略接收数据:

	_ = <-ch2
	<- ch2

循环接受:直至通道被关闭,循环才会结束!

	for d := range ch2 {
		...
	}

注意点:

  1. 通道中最多只能有一个元素!
  2. 如果通道中有一个元素,则发送阻塞,直至通道中元素被取出!
  3. 如果通道中没有元素,则接受阻塞,直至通道中被放入元素!

单向通道(了解)

只能发送的通道:var chOnlySend chan<- int = ch2 或者 make(chan<- int)
只能接受的通道:var chOnlySend <-chan int = ch2 或者 make(<-chan int)

带缓冲通道

普通通道的问题:通道中最多有一个元素,必须同步收发数据,效率较慢!

带缓冲的通道:通道中的元素最多可以放满缓存,当缓冲满时发送才会阻塞,当缓存空时接受才会阻塞!

缓存通道声明

通道实例 := make(chan 通道类型, 缓存大小)

很像切片:【注意:cap(ch),len(ch)

	ch := make(chan int,3)
	ch <- 1
	ch <- 2
	ch <- 3
	fmt.Println(cap(ch),len(ch)) /3 3
	<-ch
	fmt.Println(cap(ch),len(ch)) /3 2

通道多路复用

多路复用:一个信道监听、发送多路的数据!

通道多路复用就是一个 goroutine 同时监听(发送)多个通道,哪个case可执行就优先执行,Go提供select关键字:【注意和switch不一样!】

 / 多路监听例子:
	select {
	case ans := <-ch:
		fmt.Println("ch通道有了消息",ans)
	case ans1 := <-ch1:
		fmt.Println("ch1通道有了消息",ans1)
	case ans2 := <-ch2:
		fmt.Println("ch2通道有了消息",ans2)
	case ans3 := <-ch3:
		fmt.Println("ch3通道有了消息",ans3)
		
	default <-time.After(2*time.Second):============time.After()用法!!
		fmt.Println("请求超时!")
	}

实现webSocket

reader没有数据时会阻塞!

客户端

注:接受和发送数据是两个独立的goroutine

package main

import (
	"bufio"
	"fmt"
	"log"
	"net"
	"os"
	"strings"
)

// 通信服务客户端

func main() {

	require("127.0.0.1:65203")

}

// 请求函数
func require(address string) {
	// 拨号连接请求
	conn,err := net.Dial("tcp",address)
	if err != nil {
		log.Fatal(err)
	}
	// 连接成功
	fmt.Println("成功连接:",address,"并成功创建会话:",conn)

	handleSession(conn)
}


func handleSession(conn net.Conn) {
	// 会话开始
	fmt.Println("Session started!")

	go recover(conn)
	send(conn)
}

// 发送数据
func send(conn net.Conn) {
	// 获取用户的标准输入(本地读取)
	reader := bufio.NewReader(os.Stdin)

	f: for true {
		str,err := reader.ReadString('\n')
		if err != nil {
			fmt.Println("读取错误:",err.Error())
		}

		// 请求的数据
		str = strings.TrimSpace(str)

		// 判断数据是否为终止指令
		switch str {
		case "@close":
			conn.Close()
			break f
		default:
			fmt.Println("客户端:",str)
			// 返回服务器端响应数据
			conn.Write([]byte(str + "\n"))
		}
	}
}
// 接收数据
func recover(conn net.Conn) {
	// 创建网络连接数据的读取器
	reader := bufio.NewReader(conn)

	// 循环接受数据
	for true {
		// 读取数据 读取器中没有数据Read方法会阻塞
		str,err := reader.ReadString('\n')
		if err != nil {
			// 读取错误
			fmt.Println("发生错误! 断开连接!")
			conn.Close()
			break
		}
		// 请求的数据
		str = strings.TrimSpace(str)

		// 判断数据是否为终止指令
		if str == "@close" {
			fmt.Println("断开连接指令")
			conn.Close()
			break
		}
		fmt.Println("服务器:",str)
	}
}

服务器端

package main

import (
	"bufio"
	"fmt"
	"log"
	"net"
	"strings"
)

// 通信服务器端

func main() {

	server("127.0.0.1:65203")

}

// 侦听请求函数
func server(address string) {
	// 根据侦听地址(ip:端口号) 创建侦听器
	l,err := net.Listen("tcp",address)
	if err != nil {
		// 侦听器创建失败
		log.Fatal(err)
	}
	// 侦听器成功创建
	fmt.Println(address,"的侦听器创建成功!")
	// 延迟关闭侦听器
	defer l.Close()

	// 循环侦听连接请求 为请求创建连接
	for true {
		// 侦听请求
		conn,err := l.Accept()
		if err != nil {
			fmt.Println(err.Error())
			continue
		}
		// 成功创建会话
		fmt.Println("成功创建会话:",conn)
		// 为连接创建会话
		go handleSession(conn)
	}
}

func handleSession(conn net.Conn) {
	// 会话开始
	fmt.Println("Session started!")
	
	// 创建网络连接数据的读取器
	reader := bufio.NewReader(conn)
	
	// 循环接受数据
	for true {
		// 读取数据 读取器中没有数据Read方法会阻塞
		str,err := reader.ReadString('\n')
		if err != nil {
			// 读取错误
			fmt.Println("发生错误! 断开连接!",err.Error())
			conn.Close()
			break
		}

		str = strings.TrimSpace(str)
		fmt.Println("客户端:",str)
		fmt.Println("服务器:",str + "【服务器端响应数据】\n")
		// 返回服务器端响应数据
		conn.Write([]byte(str + "【服务器端响应数据】\n"))

	}
}

goroutine下的并发问题

查看代码是否有并发问题(竞态检测):go run -race xxx.go

原子包sync/atomic

实际是原子操作的一个工具包!(和Java原子类不同,java是直接封装数据类型,而Go的原子类是个工具包)

  1. 提供一些基本的原子操作方法
  2. 也有基于CAS操作的,类似atomic.CompareAndSwapInt64()的方法

原子包除了提供了对基本类型的原子操作,还提供了atomic.Value结构体,atomic.Value实例就可以存入我们自定义类型数据,其Store()写,Load()读方法都是原子操作:

	h := new(Host)

	var av atomic.Value

	av.Store(h)
	hh := av.Load()
	
	if t,ok := hh.(*Host);ok {
		h = t
	}

同步包sync

1. 互斥锁sync.Mutex

sync.Mutex类似Java中的Lock,但 不是可重入锁!

	var lock sync.Mutex
	var count = 100
	
	func addInt() {
		lock.Lock()
		count--
		lock.Unlock()
	}

2. 读写锁sync.RWMutex

利用读读可同时的特点,适用于读操作频繁的共享变量! 不是可重入锁!
注意:写锁 rwLock.Lock() 读锁 rwLock.RLock()

var rwLock sync.RWMutex
var count = 100

func setCount(c int)  {
	rwLock.Lock()
	count = c
	rwLock.Unlock()
}

func getCount() int {
	rwLock.RLock()
	defer rwLock.RUnlock()
	return count
}

3. 等待组sync.WaitGroup

g.Add(1)g.Wait()必须是同一个goroutine!

目的:保证并发环境中完成指定数量的任务!

内部有个计数器,这些线程安全的方法都可以保证等待组的正确性!
请添加图片描述

注:group.Wait() 函数会阻塞,直至任务数为0:

	// 任务
	tasks := make([]func(),10,10)
	// 等待组
	var group sync.WaitGroup
	// 将任务数量添加进等待组中 并且并行执行所有任务
	for _, f := range tasks {
		// 需要执行的任务数量+1
		group.Add(1)
		go func() {
			// 执行完成的任务数量-1
			defer group.Done()
			f()
		}()
	}
	/ 当任务数量为0时 此函数停止阻塞(注:此线程是将所有任务添加完后才到这里等待任务完成的 所以不会有问题)
	group.Wait()
	fmt.Println("任务全部完成!")
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值