Goroutines
基本演示
package main
import (
"fmt"
"strconv"
"time"
)
func test() {
for i := 1; i <= 10; i++ {
fmt.Println("hello golang + " + strconv.Itoa(i))
// 阻塞一秒
time.Sleep(time.Second)
}
}
// 主线程
func main() {
// 开启一个协程
go test()
for i := 1; i <= 10; i++ {
fmt.Println("hello + " + strconv.Itoa(i))
// 阻塞一秒
time.Sleep(time.Second)
}
}
启动多个携程
package main
import (
"fmt"
"time"
)
func main() {
for i := 1; i <= 5; i++ {
/* go func() {
// 匿名函数+外部变量 = 闭包了
fmt.Println(i)
}() */
// 启动一个协程
// 使用匿名函数,直接调用匿名函数
go func(n int) {
fmt.Println(n)
}(i)
}
// 主死从随
// 下面写点东西让主线程执行,否则主直接死从随
time.Sleep(time.Second * 2)
}
WaitGroup 控制携程退出
WaitGroup用于等待一组线程的结束。父线程调用Add方法来设定应等待的线程的数量。每个被等待的线程在结束时应调用Done方法。同时,主线程里可以调用Wait方法阻塞至所有线程结束。
package main
import (
"fmt"
"sync"
)
// 1. 只定义无需赋值
var wg sync.WaitGroup
func main() {
for i := 1; i <= 5; i++ {
// 2. 协程开始的时候加 1 操作
wg.Add(1)
go func(n int) {
fmt.Println(n)
// 3. 每次协程执行完成减 1
// defer 的作用是:无论函数如何退出(正常完成、提前返回、发生 panic 等),都会执行被延迟的函数调用
defer wg.Done()
}(i)
}
// 4. 主线程一直在阻塞,什么时候 wg 减为0了,就会停止
wg.Wait()
}
多个携程操作同一数据的问题
package main
import (
"fmt"
"sync"
)
var totalNum int
// 1. 只定义无需赋值
var wg sync.WaitGroup
func add() {
// 3. 每次携程完毕减 1
defer wg.Done()
for i := 0; i < 100000; i++ {
totalNum = totalNum + 1
}
}
func sub() {
// 4. 每次携程完毕减 1
defer wg.Done()
for i := 0; i < 100000; i++ {
totalNum = totalNum - 1
}
}
func main() {
// 2. 携程开始前 Add
wg.Add(2)
go add()
go sub()
wg.Wait()
fmt.Println(totalNum)
}
互斥锁
package main
import (
"fmt"
"sync"
)
var totalNum int
var wg sync.WaitGroup
// 1. 加入互斥锁
var lock sync.Mutex
func add() {
defer wg.Done()
for i := 0; i < 100000; i++ {
// 2. 加锁
lock.Lock()
totalNum = totalNum + 1
// 3. 解锁
lock.Unlock()
}
}
func sub() {
defer wg.Done()
for i := 0; i < 100000; i++ {
// 4. 加锁
lock.Lock()
totalNum = totalNum - 1
// 5. 解锁
lock.Unlock()
}
}
func main() {
wg.Add(2)
go add()
go sub()
wg.Wait()
fmt.Println(totalNum)
}
读写锁的使用
RWMutex 是一个读写锁,其经常用于读次数远远多于写次数的场景。在读的时候,数据之间不产生影响,写和读之间才会产生影响。
下面的案例:在写的过程中,锁生效,但是读可以并发读,没有影响。
package main
import (
"fmt"
"sync"
"time"
)
var totalNum int
var wg sync.WaitGroup
// 1. 加入读写锁
var lock sync.RWMutex
func read() {
defer wg.Done()
// 2. 开锁
lock.RLock() // 如果只是读数据,这个锁不产生影响,但是如果读写同时发生的时候,这个锁就会有影响
fmt.Println("开始读取数据")
time.Sleep(time.Second)
fmt.Println("读取数据成功")
// 3. 关锁
lock.RUnlock()
}
func write() {
defer wg.Done()
lock.Lock()
fmt.Println("开始修改数据")
time.Sleep(time.Second * 10)
fmt.Println("修改数据成功")
lock.Unlock()
}
func main() {
wg.Add(6)
// 读多写少
for i := 0; i < 5; i++ {
go read()
}
go write()
wg.Wait()
fmt.Println(totalNum)
}
管道
用于线程通信的,本质是一个队列数据结构,管道是引用类型。
基本操作
package main
import (
"fmt"
)
func main() {
// 1. 声明管道
var intChan chan int
// 2. 通过 make 初始化后方可使用,管道可以存放 3 个 int 类型的数据
intChan = make(chan int, 3)
// 3. 证明管道是引用类型
fmt.Printf("intChan的值:%v\n", intChan) // 0xc00007610010
// 4. 向管道存放数据
intChan <- 10
num := 20
intChan <- num
intChan <- 40
fmt.Printf("管道的实际长度:%v,管道的容量是:%v\n", len(intChan), cap(intChan)) // 管道的实际长度:3,管道的容量是:3
// 5. 注意:不能存放大于容量的数据
//intChan <- 80 // 报错:fatal error: all goroutines are asleep - deadlock!
// 6. 从管道中读取数据
num1 := <-intChan
num2 := <-intChan
num3 := <-intChan
fmt.Println(num1) // 10
fmt.Println(num2) // 20
fmt.Println(num3) // 30
// 7. 注意:在没有使用协程的情况下,如果管道的数据已经全部取出,那么再取就会报错
//num4 := <-intChan
//fmt.Println(num4)
// 8. 输出管道的长度
fmt.Printf("管道的实际长度:%v,管道的容量是:%v", len(intChan), cap(intChan)) // 管道的实际长度:0,管道的容量是:3
}
关闭管道
使用内置函数close可以关闭管道,当管道关闭后,就不能再向管道写数据了,但是仍然可以从该管道读取数据。
package main
import (
"fmt"
)
func main() {
// 1. 声明管道
var intChan chan int
// 2. 通过 make 初始化:管道可以存放 3 个 int 类型的数据
intChan = make(chan int, 3)
// 3. 在管道中存放数据:
intChan <- 10
intChan <- 20
// 4. 关闭管道
close(intChan)
// 5. 再次写入数据,报错:panic: send on closed channel
//intChan <- 30
// 6. 当管道关闭后,读取数据是可以的
num := <-intChan
fmt.Println(num) // 10
}
遍历管道
管道支持 for-range 的方式进行遍历,请注意两个细节
1. 在遍历时,如果管道没有关闭,则会出现 deadlock 的错误
2. 在遍历时,如果管道已经关闭,则会正常遍历数据,遍历完后,就会退出遍历
package main
import (
"fmt"
)
func main() {
// 1. 定义管道
var intChan chan int
// 2. 通过 make 初始化,管道可以存放 3 个 int 类型的数据
intChan = make(chan int, 3)
for i := 0; i < 3; i++ {
intChan <- i
}
// 3. 在遍历前,如果没有关闭管道,就会出现 deadlock 的错误,所以我们在遍历前要进行管道的关闭
close(intChan)
// 4. 遍历:for-range
for v := range intChan {
fmt.Println("value = ", v)
}
}
携程和管道协同工作
完成协程和管道协同工作的案例,具体要求:
1. 开启一个 writeData 协程,向管道中写入50个整数;
2. 开启一个 readData 协程,从管道中读取 writeData 写入的数据;
3. 注意: writeData 和 readDate 操作的是同一个管道;
4. 主线程需要等待 writeData 和 readDate 协程都完成工作才能退出。
package main
import (
"fmt"
"sync"
"time"
)
var wg sync.WaitGroup
func writeData(intChan chan int) {
defer wg.Done()
for i := 1; i <= 50; i++ {
intChan <- i
fmt.Println("写入的数据为:", i)
// 慢一点,看效果
time.Sleep(time.Second)
}
// 管道关闭
close(intChan)
}
func readData(intChan chan int) {
defer wg.Done()
for v := range intChan {
fmt.Println("读取的数据为:", v)
time.Sleep(time.Second)
}
}
func main() {
// 写协程和读协程共同操作同一个管道
// 1. 定义管道
intChan := make(chan int, 50)
wg.Add(2)
// 2. 开启读和写的协程
go writeData(intChan)
go readData(intChan)
// 3. 主线程一直在阻塞,什么时候 wg 减为 0 了,就停止
wg.Wait()
}
声明只读只写管道
package main
import (
"fmt"
)
func main() {
// 1. 默认情况下,管道是双向的,可读可写
// var intChan1 chan int
// 2. 声明为只写
var intChan chan<- int
intChan = make(chan int, 3)
intChan <- 20
//num := <-intChan // 报错
fmt.Println("intChan:", intChan)
// 3. 声明为只读
var intChan2 <-chan int
if intChan2 != nil {
num1 := <-intChan2
fmt.Println("num1:", num1)
}
// intChan2 <- 30 // 报错
}
管道的阻塞
当管道只写入数据,没有读取,就会出现阻塞。
package main
import (
"fmt"
"sync"
_ "time"
)
var wg sync.WaitGroup
func writeData(intChan chan int) {
defer wg.Done()
for i := 1; i <= 10; i++ {
intChan <- i
fmt.Println("写入的数据为:", i)
// time.Sleep(time.Second)
}
close(intChan)
}
func readData(intChan chan int) {
defer wg.Done()
for v := range intChan {
fmt.Println("读取的数据为:", v)
//time.Sleep(time.Second)
}
}
func main() {
// 1. 定义管道
intChan := make(chan int, 10)
wg.Add(2)
// 2. 开启读和写的协程
go writeData(intChan)
// go readData(intChan)
// 主线程一直在阻塞,什么时候 wg 减为 0 了,就停止
wg.Wait()
}
写的快,读的满(管道读写频率不一致),也不会出现阻塞问题。
package main
import (
"fmt"
"sync"
"time"
)
var wg sync.WaitGroup
func writeData(intChan chan int) {
defer wg.Done()
for i := 1; i <= 10; i++ {
intChan <- i
fmt.Println("写入的数据为:", i)
//time.Sleep(time.Second)
}
close(intChan)
}
func readData(intChan chan int) {
defer wg.Done()
for v := range intChan {
fmt.Println("读取的数据为:", v)
time.Sleep(time.Second)
}
}
func main() {
intChan := make(chan int, 10)
wg.Add(2)
go writeData(intChan)
go readData(intChan)
wg.Wait()
}
select 功能
select功能
1. 解决多个管道的选择问题,也可以叫做多路复用,可以从多个管道中随机公平地选择一个来执行
2. case 后面必须进行的是 io 操作,不能是等值,随机去选择一个 io 操作
3. default 防止 select 被阻塞住,加入 default
package main
import (
"fmt"
"time"
)
func main() {
// 1. 定义一个 int 管道
intChan := make(chan int, 1)
go func() {
time.Sleep(time.Second * 15)
intChan <- 10
}()
// 2. 定义一个 string 管道
stringChan := make(chan string, 1)
go func() {
time.Sleep(time.Second * 12)
stringChan <- "dva"
}()
// fmt.Println(<-intChan) // 本身取数据就是阻塞的
select {
case v := <-intChan:
fmt.Println("intChan:", v)
case v := <-stringChan:
fmt.Println("stringChan:", v)
default:
fmt.Println("防止 select 被阻塞")
}
}
defer + revover 机制处理错误
package main
import (
"fmt"
"time"
)
func printNum() {
for i := 1; i <= 10; i++ {
fmt.Println(i)
}
}
func devide() {
// 错误处理
defer func() {
err := recover()
if err != nil {
fmt.Println("devide()出现错误:", err)
}
}()
num1 := 10
num2 := 0
result := num1 / num2
fmt.Println(result)
}
func main() {
go printNum()
go devide()
time.Sleep(time.Second * 5)
}
反射
- 反射可以做什么?
1. 反射可以在运行时动态获取变量的各种信息,比如变量的类型,类别等信息
2. 如果是结构体变量,还可以获取到结构体本身的信息(包括结构体的字段、方法)
3. 通过反射,可以修改变量的值,可以调用关联的方法。
4. 使用反射,需要 import ("reflect")
-反射相关的函数
1. reflect.TypeOf(变量名),获取变量的类型,返回reflect.Type类型
2. reflect.ValueOf(变量名),获取变量的值,返回reflect.Value类型(reflect.Value是一个结构体类型),通过reflect.Value,可以获取到关于该变量的很多信息
package main
import (
"fmt"
"reflect"
)
// 利用一个函数,函数的参数定义为空接口
// 空接口没有任何方法,所以可以理解为所有类型都实现了空接口,也可以理解为我们可以把任何一个变量赋给空接口
func testReflect(i interface{}) {
// 1. 调用 TypeOf 函数,返回 reflect.Type 类型数据
reType := reflect.TypeOf(i)
fmt.Println("reType:", reType) // int
fmt.Printf("reType 的具体类型是:%T\n", reType) // reType的具体类型是:*reflect.rtypereValue
// 2. 调用 ValueOf 函数,返回 reflect.Value 类型数据
reValue := reflect.ValueOf(i)
fmt.Println("reValue:", reValue) // reValue: 100
fmt.Printf("reValue的具体类型是:%T\n", reValue) // reValue的具体类型是:reflect.Value
// 3. 如果真想获取 reValue 的数值,要调用 Int() 方法:返回 v 持有的有符号整数
num2 := 80 + reValue.Int()
fmt.Println(num2) // 180
// 【还有一种处理方式如下】
// 4. reValue 转成空接口
i2 := reValue.Interface()
// 5. 类型断言
n := i2.(int)
n2 := n + 30
fmt.Println(n2) // 130
}
func main() {
// 对基本数据类型进行反射
// 定义一个基本数据类型
var num int = 100
testReflect(num)
}
对结构体类型反射
package main
import (
"fmt"
"reflect"
)
func testReflect(i interface{}) {
// 1. 调用 TypeOf 函数,返回 reflect.Type 类型数据
reType := reflect.TypeOf(i)
fmt.Println("reType:", reType) // reType: main.Student
fmt.Printf("reType的具体类型是:%T\n", reType) // reType的具体类型是:*reflect.rtype
// 2. 调用 ValueOf 函数,返回 reflect.Value 类型数据
reValue := reflect.ValueOf(i)
fmt.Println("reValue:", reValue) // reValue: {丽丽 18}
fmt.Printf("reValue的具体类型是:%T\n", reValue) // reValue的具体类型是:reflect.Value
// 3. 类型断言
// reValue 转成空接口
i2 := reValue.Interface()
n, flag := i2.(Student)
if flag == true { //断言成功
fmt.Printf("学生的名字是:%v,学生的年龄是:%v\n", n.Name, n.Age) // 学生的名字是:丽丽,学生的年龄是:18
}
}
// 定义学生结构体
type Student struct {
Name string
Age int
}
func main() {
// 对结构体类型进行反射
// 定义结构体具体的实例
stu := Student{
Name: "丽丽",
Age: 18,
}
testReflect(stu)
}
获取变量的类别
package main
import (
"fmt"
"reflect"
)
func testReflect(i interface{}) {
// 1. 调用 TypeOf 函数,返回 reflect.Type 类型数据
reType := reflect.TypeOf(i)
// 2. 调用 ValueOf 函数,返回 reflect.Value 类型数据
reValue := reflect.ValueOf(i)
// 获取变量的类别
//(1)reType.Kind()
k1 := reType.Kind()
fmt.Println(k1) // struct
//(2)reValue.Kind()
k2 := reValue.Kind()
fmt.Println(k2) // struct
// 获取变量的类型,reValue 转成空接口
i2 := reValue.Interface()
// 类型断言
n, flag := i2.(Student)
if flag == true { //断言成功
fmt.Printf("结构体的类型是:%T", n) // 结构体的类型是:main.Student
}
}
type Student struct {
Name string
Age int
}
func main() {
stu := Student{
Name: "丽丽",
Age: 18,
}
testReflect(stu)
}
通过反射修改变量
package main
import (
"fmt"
"reflect"
)
func testReflect(i interface{}) {
reValue := reflect.ValueOf(i)
// 通过 SetInt() 来改变值
reValue.Elem().SetInt(40)
}
func main() {
var num int = 100
testReflect(&num)
fmt.Println(num)
}
通过反射操作结构体的属性和方法
package main
import (
"fmt"
"reflect"
)
type Student struct {
Name string
Age int
}
// 给结构体绑定方法
func (s Student) CPrint() {
fmt.Println("调用了Print()方法")
fmt.Println("学生的名字是:", s.Name)
}
func (s Student) AGetSum(n1, n2 int) int {
fmt.Println("调用了 AGetSum 方法")
return n1 + n2
}
func (s Student) BSet(name string, age int) {
s.Name = name
s.Age = age
}
// 定义函数操作结构体进行反射操作
func TestStudentStruct(a interface{}) {
// 1. a 转成 reflect.Value 类型
val := reflect.ValueOf(a)
fmt.Println(val) // {丽丽 18}
// 2. 通过 reflect.Value 类型操作结构体内部的字段
n1 := val.NumField()
fmt.Println(n1) // 2
// 3. 遍历-获取具体的字段
for i := 0; i < n1; i++ {
//第0个字段的值是:丽丽
//第1个字段的值是:18
fmt.Printf("第%d个字段的值是:%v\n", i, val.Field(i))
}
// 4. 通过 reflect.Value 类型操作结构体内部的方法
n2 := val.NumMethod()
fmt.Println(n2)
// 调用 CPrint() 方法
// 调用方法,方法的首字母必须大写才能有对应的反射的访问权限
// 方法的顺序按照 ASCII 的顺序排列的,a,b,c...索引:0,1,2...
val.Method(2).Call(nil) // 调用了Print()方法,学生的名字是: 丽丽
// 5. 定义 Value 的切片
var params []reflect.Value
params = append(params, reflect.ValueOf(10))
params = append(params, reflect.ValueOf(20))
result := val.Method(0).Call(params) // 调用了 AGetSum 方法
fmt.Println("AGetSum方法的返回值为:", result[0].Int()) // AGetSum 方法的返回值为:30
}
func main() {
s := Student{
Name: "丽丽",
Age: 18,
}
TestStudentStruct(s)
}
通过反射修改变量的值。
package main
import (
"fmt"
"reflect"
)
type Student struct {
Name string
Age int
}
func TestStudentStruct(a interface{}) {
// 1. a 转成 reflect.Value 类型
val := reflect.ValueOf(a)
fmt.Println(val) // &{丽丽 18}
n := val.Elem().NumField()
fmt.Println(n) // 2
// 2. 修改字段的值
val.Elem().Field(0).SetString("张三")
}
func main() {
// 定义结构体具体的实例
s := Student{
Name: "丽丽",
Age: 18,
}
TestStudentStruct(&s)
fmt.Println(s) // 张三 18}
}