### sync包
go语言多线程,子线程运行时间大于主线程时,主线程运行完毕就会关闭程序,届时子线程没运行完。
示例代码:创建一个主线程和一个test的子线程,主线程和子线程都使用for循环输出i,子线程时间间隔为3秒,主线程为1秒。子线程执行时间大于主线程
```
package main
import (
"fmt"
"time"
)
// 子进程函数
func test() {
for i := 0; i < 10; i++ {
fmt.Println("子线程输出", i)
// 这里加一个子线程堵塞时间间隔 3秒 比主线程少1秒
time.Sleep(time.Second * 3)
}
}
// main函数的是主线程
func main() {
// 如果不加关键词 go 就会顺序执行 先执行 test函数的
// 加关键词 go 但是test函数没有执行, 是因为主线程运行太快直接结束关闭了程序
go test()
for i := 0; i < 10; i++ {
fmt.Println("主线程输出", i)
// 这里个主线程加一个时间堵塞,但是时间比子线程少
time.Sleep(time.Second)
}
// 所以 需要加 一个休眠时间堵塞一下
time.Sleep(time.Second)
fmt.Println("主线程结束")
}
//总结:
// go语言多线程 需要加时间堵塞 才能运行
// 子线程运行时间大于主线程时,主线程运行完毕就会关闭程序,届时子线程没运行完
```
基于上面的示例代码,使用sync包里面**sync.WaitGroup** (等待组)
示例代码:和上面一样,这里使用sync包里面的等待组sync.WaitGroup
第一步创建全局变量,wg类型sync.WaitGroup
第二步使用Add()函数协程计数器加1
第三步,子进程结束使用Done()函数协程计数器减1
第四步主进程使用Wait()函数等待子进程
```
package main
import (
"fmt"
"sync"
"time"
)
// 第一步 创建一个全局变量 wg 类型是sync.WaitGroup
var wg sync.WaitGroup
// 子进程函数
func test() {
for i := 0; i < 10; i++ {
fmt.Println("子线程输出", i)
time.Sleep(time.Second * 2)
}
// 第三步,子进程结束 使用sync包里面的 Done()函数 协程计数器加-1
wg.Done()
}
// main函数的是主线程
func main() {
// 第二步,调用协程时,使用sync包里面的 Add()函数 协程计数器加1
wg.Add(1)
go test()
for i := 0; i < 10; i++ {
fmt.Println("主线程输出", i)
time.Sleep(time.Second)
}
// 第四步,调用协程时,使用sync包里面的 Wait()函数 主线程等待子线程结束
wg.Wait()
fmt.Println("主线程结束")
}
// 总结:这样就会一直等到子线程直线完毕 程序才关闭
```
```
package main
import (
"fmt"
"sync"
)
func main() {
wg := sync.WaitGroup{}
wg.Add(100)
for i := 0; i < 100; i++ {
go func(i int) {
fmt.Println(i)
wg.Done()
}(i)
}
wg.Wait()
}
```
多个协程示例代码
```
package main
import (
"fmt"
"sync"
"time"
)
var wg sync.WaitGroup
// 子进程函数
func test(num int) {
for i := 0; i < 10; i++ {
fmt.Println("线程", num, "输出", i)
// time.Sleep(time.Second * 2) 堵塞2秒
time.Sleep(time.Millisecond * 100) // 100毫秒
}
wg.Done()
}
// main函数的是主线程
func main() {
for i := 0; i < 3; i++ {
wg.Add(1)
go test(i)
}
for i := 0; i < 10; i++ {
fmt.Println("主线程输出", i)
time.Sleep(time.Millisecond * 20) // 20毫秒
}
wg.Wait()
fmt.Println("主线程结束")
}
```
互斥锁 **sync.Mutex**
加互斥锁主要解决(编译后)资源竞争的问题。比如多个线程抢占一个资源,例如多个协程读取数据库里面的一条内容。加了锁之后,只有一个协程读取,其他的等待它读取完再执行操作。
示例代码:创建变量j初始值为0,test函数执行一次,j就会递增。如果多个进程同时使j递增的话,就可能因为进程抢占资源报错。但是加了互斥锁之后,就会等一个协程执行完之后,再执行另外一个。就不会发生资源抢占而报错。
```
package main
import (
"fmt"
"sync"
)
// 逻辑中使用的某个变量
var j = 0
// 与变量对应的使用互斥锁
var mutex sync.Mutex
// 等待组
var wg sync.WaitGroup
// 子进程
func test() {
// 逻辑开始加互斥锁
mutex.Lock()
// 逻辑遍历递增
j++
fmt.Println("逻辑变量", j)
// 去逻辑锁
mutex.Unlock()
// 协程计数器减1
wg.Done()
}
//主进程
func main() {
for i := 0; i < 10; i++ {
// 协程计数器加1
wg.Add(1)
go test()
}
// 等待子进程结束关闭程序
wg.Wait()
}
```
读写互斥锁 **sync.RWMutex**
读写互斥锁定,可以让多个读操作并发,同时读取,但是对于写操作是完全互斥的。也就是说当一个协程进行写操作的时候,其他协程不能进行读操作也不也能写操作。
示例代码:10个进程执行写的操作,10个进程执行读的操作,加上读写互斥锁之后,写的操作是互斥的,只能一个进程操作完,下个进程才能操作。读的时候是并行的。
```
package main
import (
"fmt"
"sync"
"time"
)
// 等待组
var wg sync.WaitGroup
// 读写互斥锁
var mutex sync.RWMutex
// 写操作的协程
func write() {
// 加互斥锁
mutex.Lock()
fmt.Println("@@@执行写操作")
time.Sleep(time.Millisecond * 1000) //1000毫秒
// 去锁
mutex.Unlock()
wg.Done()
}
func read() {
// 加读写互斥锁
mutex.RLock()
fmt.Println("执行读操作$$$")
time.Sleep(time.Millisecond * 1000) //1000毫秒
// 去锁
mutex.RUnlock()
wg.Done()
}
func main() {
// 10个协程执行写的操作
for i := 0; i < 10; i++ {
wg.Add(1)
go write()
}
// 10协程执行读的操作
for i := 0; i < 10; i++ {
wg.Add(1)
go read()
}
wg.Wait()
}
```
- 安装开发环境
- 安装开发环境
- 安装详细教程
- 引入包
- Go语言基础
- 基本变量与数据类型
- 变量
- 数据类型
- 指针
- 字符串
- 代码总结
- 常量与运算符
- 常量
- 运算符
- 流程控制
- if判断
- for循环
- switch分支
- goto跳转
- 斐波那契数列
- Go语言内置容器
- 数组
- 切片
- 映射
- 函数
- 函数(上)
- 函数(中)
- 函数(下)
- 小节
- 包管理
- 结构体
- 结构体(上)
- 结构体(中)
- 结构体(下)
- 小节
- 错误处理
- 错误处理
- 宕机
- 错误应用
- 小节
- 文件操作
- 获取目录
- 创建和删除目录
- 文件基本操作(上)
- 文件基本操作(中)
- 文件基本操作(下)
- 处理JSON文件
- 接口与类型
- 接口的创建与实现
- 接口赋值
- 接口嵌入
- 空接口
- 类型断言(1)
- 类型断言(2)
- 小节
- 并发与通道
- goroutine协程
- runtime包
- 通道channel
- 单向通道channel
- select
- 线程同步
- 多线程的深入学习
- http编程
- http简介
- Client和Request
- get请求
- post请求
- 模块函数方法
- 模块
- fmt库,模块
- 项目练习
- 爬虫:高三网
- 爬虫:快代理
- 爬虫:快代理2
- 多线程:通道思路
- 多线程爬虫:快代理