首页 > 其他分享 >golang 爬虫修炼02 ---协程、互斥锁、读写锁、waitgroup

golang 爬虫修炼02 ---协程、互斥锁、读写锁、waitgroup

时间:2024-06-20 20:29:34浏览次数:12  
标签:02 wg 协程 lock --- 互斥 线程 func totalNum

协程

程序:为了完成特定任务,使用某种语言编写的一组指令的集合,是一段静态的代码

进程:是程序的一次执行过程。正在运行的一个程序,进程作为资源分配的单位,在内存中会为每个进程分配不同的内存区域。进程是动态的,有产生、存在、消亡的过程

线程:进程可进一步细分为线程,是一个程序内部的一条执行路径,若一个进程同一时间并行执行多个线程,就是支持多线程的

协程:又称为微线程、纤程,协程是一种用户态的轻量级线程 作用:在执行A函数的时候可以随时中断去执行B函数,然后中断继续执行A函数(可以自由切换),注意这一切换过程并不是函数调用,过程很像多线程,但是实际只是一个线程,有一个特点就是主死从随(协程随着主线程死亡一起死亡)

类似如下图所示,将消耗时间和资源的io操作放在一边

在这里插入图片描述

对于单线程下,我们不可避免程序中出现i0操作,但如果我们能在自己的程序中(即用户程序级别,而非操作系统级别)控制单线程下的多个任务能在-个任务遇到io阻塞时就将寄存器上下文和栈保存到某个其他地方,然后切换到另外一个任务去计算。在任务切回来的时候,恢复先前保存的寄存器上下文和栈,这样就保证了该线程能够最大限度地处于就绪态,即随时都可以被cpu执行的状态,相当于我们在用户程序级别将自己的i0操作最大限度地隐藏起来,从而可以迷惑操作系统,让其看到:该线程好像是一直在计算,io比较少,从而会更多的将cpu的执行权限分配给我们的线程(注意:线程是CPU控制的,而协程是程序自身控制的,属于程序级别的切换,操作系统完全感知不到,因而更加轻量级)

一、协程

单个协程

go语言开启协程非常简单,只需在调用函数前加一个go

例: go test()

func test() {
    for i := 1; i <= 10; i++ {
       fmt.Println("hello golang", strconv.Itoa(i))
       //阻塞1s
       time.Sleep(time.Second * 1)
    }
}

func main() { //主线程
    go test() //开启协程
    for i := 1; i <= 10; i++ {
       fmt.Println("hello 秋刀鱼", strconv.Itoa(i))
       //阻塞1s
       time.Sleep(time.Second * 1)
    }
}

在这里加入

time.Sleep(time.Second * 1)

是为了防止主线程死掉导致没有时间给协程

运行结果:

在这里插入图片描述

查看结果就会发现在交替运行

在这里插入图片描述

多个协程
func main() { //主线程

    for i := 0; i <= 5; i++ {
       // 启动多个协程  (使用匿名函数)
       go func() {
          fmt.Println(i)
       }()
    }
    time.Sleep(1 * time.Second)
}

输出

在这里插入图片描述

为什么输出的不是1 2 3 4 5 6

这是因为for循环在这里是主线程,协程输出的是i变量是共享主线程的i,可能导致这种情况

想要输出无重复,直接将协程的匿名函数加一个变量,将i传入就去就行

二、WaitGroup

WaitGroup用于等待一组线程的结束,父线程调用Add方法来设定应等待的线程的数量。每个被等待的线程在结束时应调用Done方法。同时主线程里可以调用Wait方法阻塞至所有线程结束。—》解决主线程在子协程结束后自动结束

阻塞主线程,等待协程结束一起结束
// 只定义无需赋值
var wg sync.WaitGroup

func main() { //主线程
    for i := 1; i <= 5; i++ {
       wg.Add(1) //协程开始的时候加1
       go func(n int) {
          fmt.Println("hello world", n)
          wg.Done() //协程结束的时候减1
       }(i)
    }
    // 阻塞主线程  当wg减为0的时候阻塞就停止
    wg.Wait()
}

var wg sync.WaitGroup 首先定义一个变量 数据类型为sync包内一个名为WaitGroup的结构体

wg.Add(1)然后在计数器内加入协程数量

wg.Done()每结束一次协程就减去1

wg.Wait()阻塞主线程 当wg减为0的时候阻塞就停止

多个协程操作同一个数据案例

多个协程操作同一个数据会导致结果不理想

请阅读下列代码

// 只定义无需赋值
var wg sync.WaitGroup
var totalNum int

func add() {
    defer wg.Done()
    for i := 0; i < 100000; i++ {
       totalNum += 1
    }
}

func sub() {
    defer wg.Done()
    for i := 0; i < 100000; i++ {
       totalNum -= 1
    }
}

func main() { //主线程
    wg.Add(2)
    go add()
    go sub()
    wg.Wait()
    time.Sleep(time.Second * 2)
    fmt.Println(totalNum)
}

在理想的情况下,不管两个协程的函数如何交替,最后输出的结果都应该为0,但是这里输出的是

在这里插入图片描述

每次执行结束结果都不一定,为什么呢

请看下图是两个函数交替执行一个轮回的图

在这里插入图片描述

解读:

假如第一步执行的是add函数首先获取原始totalNum的值,为0,然后第二部是sub函数获取原始totalNum的值,为0,第三、四步add函数对totalNum进行加一操作并赋值给totalNum,但是第五第六步却是将totalNum的原始数据0减1后赋值给totalNum,导致一轮下来不会为0

所以说直接使用多协程操作同一个数据会导致资源竞争问题

每次执行结束结果都不一定,为什么呢

请看下图是两个函数交替执行一个轮回的图

[外链图片转存中…(img-NkB0TRcZ-1718868327943)]

解读:

假如第一步执行的是add函数首先获取原始totalNum的值,为0,然后第二部是sub函数获取原始totalNum的值,为0,第三、四步add函数对totalNum进行加一操作并赋值给totalNum,但是第五第六步却是将totalNum的原始数据0减1后赋值给totalNum,导致一轮下来不会为0

所以说直接使用多协程操作同一个数据会导致资源竞争问题

互斥锁

由多个协程操作同一个数据案例所展现的问题来看,需要一个机制来确保一个协程在执行的时候其他协程不执行,这就得用上互斥锁了

互斥锁概念:其中Mutex为互斥锁,Lock()加锁,Unlock()解锁,使用Lock()加锁后,便不能再次对其进行加锁,直到利用Unlock()解锁对其解锁后,才能再次加锁,适用于读写不确定场景,即读写次数没有明显的区别

创建互斥锁:

var lock sync.Mutex

加锁:

lock.Lock()

解锁:

lock.Unlock()

加锁后的完整代码

// 只定义无需赋值
var wg sync.WaitGroup
var totalNum int

// 加入互斥锁
var lock sync.Mutex

func add() {
    defer wg.Done()
    for i := 0; i < 100000; i++ {
       // 加锁
       lock.Lock()
       totalNum += 1
       // 解锁
       lock.Unlock()
    }
}

func sub() {
    defer wg.Done()
    for i := 0; i < 100000; i++ {
       // 加锁
       lock.Lock()
       totalNum -= 1
       // 解锁
       lock.Unlock()
    }
}

func main() { //主线程
    wg.Add(2)
    go add()
    go sub()
    wg.Wait()
    fmt.Println(totalNum)
}

执行后达成理想状态,结果为0

读写锁

RWMutex是一个读写锁,其经常用于读次数远远多于写次数的场景,和互斥锁的区别:读写锁只锁住写的操作

在读的时候,数据之间不产生影响,写和读之间才会产生影响

创建读写锁:

var lock sync.RWMutex

加锁:

lock.RLock()

闭锁:

lock.RUnlock()

上代码案例

var wg sync.WaitGroup

// 加入读写锁
var lock sync.RWMutex

func read() {
    defer wg.Done()
    // 如果只是读数据,那么这个锁不产生影响,但是读写同时发生的时候,就会有影响
    lock.RLock()
    fmt.Println("开始读取数据")
    time.Sleep(time.Second) //模拟读的时间
    fmt.Println("读取数据成功")
    lock.RUnlock()
}

func write() {
    defer wg.Done()
    lock.RLock()
    fmt.Println("开始修改数据")
    time.Sleep(time.Second * 10) //模拟写的时间
    fmt.Println("修改数据成功")
    lock.RUnlock()
}

func main() { //主线程
    wg.Add(6)
    // 启动协程  --->  场合:读多写少
    for i := 0; i < 5; i++ {
       go read()
    }
    go write()
    wg.Wait()
}

执行后结果
在这里插入图片描述

标签:02,wg,协程,lock,---,互斥,线程,func,totalNum
From: https://blog.csdn.net/weixin_67289581/article/details/139834074

相关文章

  • Python基础-类与对象
    1.面向对象的三大特性封装继承多态2.类与对象的理解与封装特性类是事物抽象的集合,对象是事物具象的个体。(类–>实例化–>对象)面向对象编程语言类:一个模板,(人类)—是一个抽象的,没有实体的对象:(eg:张三,李四)属性:(表示这类东西的特征,眼睛,嘴巴,鼻子)方法:(......
  • qt开发-03——信号与槽
    信号与槽(Signal&Slot)是Qt编程的基础,也是Qt的一大创新。因为有了信号与槽的编程机制,在Qt中处理界面各个组件的交互操作时变得更加直观和简单。信号(Signal)就是在特定情况下被发射的事件,例如PushButton最常见的信号就是鼠标单击时发射的clicked()信号,一个ComboBox......
  • qt开发-05_QPushButton
    按钮是最常用的控件;如果找不到文件可以这样:选择这个复制文件的路径,粘贴就可以了。在qt中新建一个项目,并且打开ui界面添加一个按钮;右键这个按钮可以有很多功能:先是这个转到槽,这个就是给按钮的动作添加效果的功能:这里有很多类的槽方法。都是源于他继承的父类。我们选择......
  • 三子棋-后带源码
    在函数声明调用的时候说明了日后写复杂的项目直接写在一个源文件中不建议所以今天这个三子棋分多个文件来写首先来介绍用sest来作用游戏的开始和玩法,再然后用saq.h用来存放函数的声明和头文件最后使用szq.c来实现游戏首先是游戏的开始逻辑每个游戏都要有开始界面然后就......
  • 【产品经理修炼之道】- 解构电商、O2O:装进口袋的“利是” -优惠券系统
    编辑导语:优惠券的本质是以让利的方式,吸引更多用户去购买商品,是非常重要的一个促销手段。本文作者结合自己的业务经历,整理出了优惠券系统的搭建过程,希望能给你带来帮助。优惠券在发放前要进行基础属性的配置,而合理的优惠券发放系统不仅仅只是配置张数和金额即可,还需要设置很多......
  • 计算机网络:应用层 - 万维网 & HTTP协议
    计算机网络:应用层-万维网&HTTP协议万维网WWW统一资源定位符URL超文本传输协议HTTP非持续连接持续连接非流水线流水线代理服务器HTTP报文万维网WWW万维网是一个大规模的、联机式的信息储藏所。万维网用链接的方法能非常方便地从互联网上的一个站点访问另......
  • Ant-Design-Vue动态表头并填充数据(含示例代码)
    关注我,持续分享逻辑思维&管理思维&面试题;可提供大厂面试辅导、及定制化求职/在职/管理/架构辅导;推荐专栏《10天学会使用asp.net编程AI大模型》,目前已完成所有内容。一顿烧烤不到的费用,让人能紧跟时代的浪潮。从普通网站,到公众号、小程序,再到AI大模型网站。干货满满。学成后可......
  • python 基础习题6--for循环和while循环
    1.用for循环打印1--10这10个数字,格式如下:运行结果如下: 12345678910 2.用 while循环打印1-10这个10个数字,请在横线处填空:counter=1whilecounter<= ________:print(counter)___________ 3.请问这段代码返回什么结果:(可以在环境中......
  • 算法题---判断链表中是否有环,并找出环的入口
    方案一、利用Set集合不会重复的原理booleanjudgeCycle(Nodehead){Set<Node>visited=newHashSet<>();Nodenode=head;while(node!=null){if(visited.contains(node))returntrue;visi......
  • 【代码】--库函数学习 ftp通信 相关
    1. FTP介绍 (1)主动模式(PORT): 服务器主动去连接客户端的数据端口 (2)被动模式(PASV): 客户端主动去连接服务器的数据端口ftp客户端通信流程(编程流程)如下:1.客户端用账号、密码进行登录。2.提交主动模式还是被动模式。3.如果是被动模式,需要去连接服务器开放的数据......