协程goroutine
- 使用:
go 函数名( 参数列表 )
,当然也可以调用匿名函数 或 闭包。 - main函数会默认开启一个主
goroutine
【main函数结束,所有的goroutine
都会结束】 goroutine
是由Go语言在运行时调度(直接在用户态调度),而线程是由操作系统调度!(需要转换为核心态)goroutine
可以并发的,可以抢占式任务处理!- Go1.5后默认执行
runtime.GOMAXPROCS(runtime.NumCPU())
,根据CPU数控制Go进程中的线程数! 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 {
...
}
注意点:
- 通道中最多只能有一个元素!
- 如果通道中有一个元素,则发送阻塞,直至通道中元素被取出!
- 如果通道中没有元素,则接受阻塞,直至通道中被放入元素!
单向通道(了解)
只能发送的通道: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的原子类是个工具包)
- 提供一些基本的原子操作方法
- 也有基于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("任务全部完成!")