当前位置: 首页 > wzjs >正文

搜狗新闻源网站怎么做新冠疫苗接种最新消息

搜狗新闻源网站怎么做,新冠疫苗接种最新消息,电子商务网站安全性能主要包括,高中学校网站模板目录 channel 的概念?channel 有哪些状态?如何判断 channel 已关闭?channel 的底层实现原理?channel 发送数据和接收数据的过程?channel 是否线程安全?channel 如何实现线程安全?channel 的应用…

目录

  • channel 的概念?
  • channel 有哪些状态?
  • 如何判断 channel 已关闭?
  • channel 的底层实现原理?
  • channel 发送数据和接收数据的过程?
  • channel 是否线程安全?
  • channel 如何实现线程安全?
  • channel 的应用场景?
    • 任务定时
    • 定时任务
    • 解耦生产者和消费者
    • 控制并发数
    • select 的用途?
  • defer 的概述?
  • defer 的使用场景?
    • 并发处理
    • 资源释放
    • panic-recover
  • defer 的底层原理?
  • defer 函数和 return 的执行顺序?
  • WaitGroup 使用的注意事项?
    • 补充:什么是 WaitGroup?
    • 注意事项
    • 使用示例
    • 与 channel 对比

channel 的概念?

channel 又称为管道,用于数据传递和数据共享,本质上是先进先出的队列,使用 goroutine + channel 进行数据通信非常高效。同时,channel 是线程安全的,多个 goroutine 可以同时修改一个 channel,不需要加锁。

channel 有哪些状态?

  • nil:未初始化的状态,只进行了赋值,或手动赋值为 nil;
  • active:正常的 channel,可读可写;
  • closed:已关闭,channel 的值不是 nil。关闭状态的 channel 仍可以读,但不能写
    在这里插入图片描述
    注意,上图当中的空 channel 指的是状态为 nil 的 channel,而不是活跃状态下没有数据的 channel。

如何判断 channel 已关闭?

if v, ok := <- ch; !ok {fmt.Println("channel is already closed")
}

channel 的底层实现原理?

channel 有几个重要的字段:

  • buf 指向底层的循环数组,只有设置为有缓存的 channel 才会有 buf(在 make 一个 channel 的时候,指定 make 的第二个参数);
  • sendx 和 recvx 分别指向底层循环数组的发送和接收元素位置的索引
  • sendq 和 recvq 分别表示发送数据的被阻塞的 goroutine 和读取数据的 goroutine,二者都是双向链表结构;
  • sendq 和 recvq 是等待队列类型;
  • sudog 是对 goroutine 的封装;
type hchan struct {qcount   uint           // channel中的元素个数dataqsiz uint           // channel中循环队列的长度buf      unsafe.Pointer // channel缓冲区数据指针elemsize uint16         // buffer中每个元素的大小closed   uint32         // channel是否已经关闭,0未关闭elemtype *_type // channel中的元素的类型sendx    uint   // channel发送操作处理到的位置recvx    uint   // channel接收操作处理到的位置recvq    waitq  // 等待接收的sudog(sudog为封装了goroutine和数据的结构)队列由于缓冲区空间不足而阻塞的goroutine列表sendq    waitq  // 等待发送的sudog队列,由于缓冲区空间不足而阻塞的goroutine列表lock mutex   // 一个轻量级锁
}

channel 发送数据和接收数据的过程?

channel 发送数据的过程

  • 检查 recvq 是否为空,如果不为空,则从 recvq 头部取一个 goroutine,将数据发送过去,并唤醒对应的 goroutine;
  • 如果 recvq 为空,则将数据放入到 buffer 中;
  • 如果 buffer 已满,则将要发送的数据和当前 goroutine 打包成 sudog 对象放入到 sendq 中。并将当前 goroutine 设置为 waiting 状态。

channel 接收数据的过程

  • 检查 sendq 是否为空,如果不为空,且没有缓冲区,则从 sendq 头部取一个 goroutine,将数据取出来,并唤醒对应的 goroutine,结束读取的过程【sendq 中发送数据的 goroutine 由于没有缓冲区,处于 waiting(即阻塞)状态,sendq 也是一个队列,如果此时有 goroutine 从 sendq 对头的 goroutine 取数据,那么取走数据之后,sendq 对头的 goroutine 将出列,并结束 waiting 状态】;
  • 如果 sendq 不为空,且有缓冲区,则说明缓冲区已满,此时从缓冲区首部读出数据,把 sendq 头部的 goroutine 数据写入到缓冲区尾部,并将 goroutine 唤醒,结束读取过程;
  • 如果 sendq 为空,缓冲区有数据,则直接从缓冲区取数据;
  • 如果 sendq 为空,且缓冲区没有数据或没有缓冲区,则当前的 goroutine 加入到 recvq,并进入 waiting 状态,等待输入数据到 channel 的 goroutine 将其唤醒。

注意事项

  • sendq 和 recvq 这些队列都是单个 channel 内部的成员,因此其中保存的 goroutine 都是要操作当前 channel 的 goroutine;
  • 对于没有缓冲区的 channel,当 goroutine A 向 channel 写入数据时,比如保证有另一个 goroutine B 读取,如果没有 B 读取,那么 A 将进入 sendq,状态变为 waiting,即阻塞地等待另一个 goroutine 读取数据;
  • 同理,对于没有缓冲区的 channel,goroutine B 直接从中读取数据将直接使 goroutine 进入 waiting 状态,直到有 goroutine A 向 channel 输入数据;
  • 理清楚缓冲区大小为 1 和 没有缓冲区的 channel 之间的区别:没有缓冲区的 channel 要求 goroutine 在读取时,必须有一个 goroutine 在阻塞地写入,否则读取的 goroutine 阻塞等待数据写入;反之,goroutine 写入没有缓冲区的 channel 时,必须有一个 goroutine 在阻塞地等待接收数据,否则写入的 goroutine 阻塞。对于缓冲区大小为 1 的 channel,写入的 goroutine 可以在 buffer 为空时直接写入,此时 buffer 满了,其它 goroutine 写入时将被阻塞;如果 buffer 为空,有 goroutine 从 buffer 读取数据也将被阻塞,而如果 buffer 满了,即其中有一个数据,那么读取的 goroutine 可以直接将数据读走。

channel 是否线程安全?

channel 是线程安全的。

不同 goroutine 通过 channel 进行通信,本身的使用场景就是多线程,为了保证数据的一致性,必须实现线程安全。

channel 如何实现线程安全?

channel 的底层实现中,hchan 结构体使用 mutex 锁确保数据读写的按权。在对 hchan 中 buf 的数据进行入队和出队的操作时,必须先获取互斥锁,才能操作 channel 中的数据。

channel 的应用场景?

任务定时

select {case <- time.After(time.Second)
}

time.After 返回一个 channel(<- chan time.Time),在指定的时间间隔后,该 channel 会收到一个时间值。time.After 常用于超时控制或延迟操作,特点是只能触发一次。

一个更高阶的例子如下:

select {
case <-time.After(2 * time.Second):fmt.Println("2秒后执行")
case <-someOtherChannel:fmt.Println("其他通道先收到消息")
}

在该例中,如果 someOtherChannel 在 2 秒内没有收到消息,time.After 会在 2 秒后触发,执行相应的操作。

定时任务

select {case <- time.Tick(time.Second)
}

time.Tick 返回一个 channel(<- chan time.Time),每隔指定的时间间隔,这个 channel 会收到一个时间值。time.Tick 的用途是定时任务或周期性操作time.Tick 的特点是会持续触发,每隔指定的时间间隔发送一次时间值到通道。

一个更高阶的例子如下:

ticker := time.Tick(1 * time.Second)
for {select {case <-ticker:fmt.Println("每秒执行一次")case <-someOtherChannel:fmt.Println("其他通道收到消息")return}
}

在该例中,time.Tick 会每个 1 秒发送一次时间值到通道,直到 someOtherChannel 收到消息为止。

注意事项
在 Golang 当中,select 是单次执行的,如果没有 for 循环,那么 select 在单次执行之后会退出,继续执行 select 语句块之后的代码。

解耦生产者和消费者

基于 channel 可以将生产者和消费者解耦,生产者只需要向 channel 发送数据,而消费者只管从 channel 中读取数据。

以 Zinx 框架当中的 Connection 类型的读写分离模型为例,Connection 在 Start 之后,会分别通过 StartReader 和 StartWriter 两个 goroutine 分别开启从 TCP 连接中读取数据和向连接中发送数据的 goroutine。StartReader 在读取数据并进行业务处理之后,得到了业务数据,这个时候要回写到 conn 当中。非解耦的做法是直接在 StartReader 当中将数据写回到 conn 当中,而读写分离的逻辑是通过 channel 将业务处理的结果发送到 StartWriter 这个 goroutine,在这个 goroutine 中将数据回写到 conn 当中。

控制并发数

以爬虫为例,如果需要爬取 1w 条数据,需要并发爬取以提升效率,但并发量不能过大,可以通过 channel 来控制并发规模,比如同时支持 5 个并发任务:

ch := make(chan int, 5)
for _, url := range urls {go func {ch <- 1worker(url)<- ch}
}

select 的用途?

select 可以理解为在语言层面实现了和 I/O 多路复用类似的功能:监听多个描述符的读/写事件,一旦某个描述符就绪(一般是读或写事件发生了),就能够将发生的事件通知给关心的应用程序去处理该事件。

golang 的 select 机制如下:监听多个 channel,每个 case 是一个事件,可以是读事件也可以是写事件,随机选择一个执行。可以设置 default,其作用是在监听的多个事件都阻塞时,执行 default 逻辑:

select {case <-ch1:// 如果从 ch1 信道成功接收数据,则执行该分支代码case ch2 <- 1:// 如果成功向 ch2 信道成功发送数据,则执行该分支代码default:// 如果上面都没有成功,则进入 default 分支处理流程
}

注意事项

  • select 语句只能用于 channel 的读写操作;
  • select 中的 case 条件(非阻塞)是并发执行的,select 会选择先操作成功的那个 case 去执行,如果多个 case 同时成立,则随机选择一个执行,因此无法保证顺序;
  • 对于 case,如果存在 channel 为 nil 的情况,则该分支将被忽略,可以理解为从 select 中删除了这个 case;
  • 可以设置一个任务定时的 case,比如使用time.After,它通常会替代 default,即:如果在指定时间内没有任务执行,那么就执行time.After这个 case 对应的语句块,否则执行相应的成立的 case;
  • 空的 select{} 会引起 deadlock;
  • 对于 for loop 当中的 select{}可能会引起 CPU 占用过高的问题

defer 的概述?

defer 是 golang 提供的一种用于注册延迟调用的机制:defer 能够让函数或语句在当前函数执行完毕之后(包括 return 正常结束和 panic 导致的异常退出)进行调用。

defer 的特性:

  • 延迟调用:defer 在 main return 前调用,且 defer 必须置于函数内部;
  • LIFO:Last In First Out,后进先出,压栈式执行;
  • 作用域:如果一个 defer 处于某个匿名函数当中,那么会先调用这个匿名函数中的 defer。

defer 的使用场景?

defer 通常出现在一些成对操作中,比如创建和关闭连接、加锁和解锁、打开文件与关闭文件等。总得来说,defer 在一些资源回收的场景中很有用。

并发处理

var wg sync.WaitGroupfor i := 0; i < 2; i++ {wg.Add(1)go func() {defer wg.Done()// 程序逻辑}()
}
wg.Wait()

	mu.RLock()defer mu.RUnlock()

资源释放

// new 一个客户端 client;
cli, err := clientv3.New(clientv3.Config{Endpoints: endpoints})
if err != nil {log.Fatal(err)
}
// 释放该 client ,也就是说该 client 的声明周期就只在该函数中;
defer cli.Close()

panic-recover

defer func() {if v := recover(); v != nil {_ = fmt.Errorf("PANIC=%v", v)}
}()

defer 的底层原理?

defer 的结构中主要包括:siz 属性,用于标识返回值的内存和大小;heap 属性,用于标识该结构在栈上分配还是在堆上分配;sp 是栈指针、pc 是程序计数器、fn 是传入的函数地址、link 是 defer 链表。

type _defer struct {siz     int32 // 参数和返回值的内存大小started boolheap    bool    // 区分该结构是在栈上分配的,还是对上分配的sp        uintptr  // sp 计数器值,栈指针;pc        uintptr  // pc 计数器值,程序计数器;fn        *funcval // defer 传入的函数地址,也就是延后执行的函数;_panic    *_panic  // panic that is running deferlink      *_defer   // 链表
}

link 将 defer 串成一个链表,表头是挂载在 goroutine 的 _defer 属性。defer 结构只是一个头结构,后面跟着延迟函数的参数和返回值空间,内存在defer关键字执行的时候填充。

defer 函数和 return 的执行顺序?

执行顺序如下:

首先,return 语句执行:

  • return 先计算返回值(如果有返回值的话),并将返回值存储到函数的返回变量中;
  • 如何返回值是命名返回值(named return value),return 会将值赋给命名变量;

之后,defer 函数链执行:

  • return 完成返回值计算后,defer 开始执行;
  • defer 可以访问和修改命名的返回值

最后,函数真正地返回:

  • defer 函数链执行完毕后,函数才真正地返回给调用者。

WaitGroup 使用的注意事项?

补充:什么是 WaitGroup?

在 Golang 当中,sync.WaitGroup 是一个用于等待一组 goroutine 完成执行的同步工具。它非常适合在需要等待多个并发任务完成后再继续执行的场景。

WaitGroup 的作用

  • goroutine 计数:通过计数器跟踪正在执行的 goroutine 数量;
  • 阻塞等待:主 goroutine 可以调用 Wait 方法阻塞,直到所有被跟踪的 goroutine 完成执行;
  • 动态增减:可以在运行时动态增减 goroutine 的计数;

WaitGroup 的核心方法
(1)Add(delta int)

  • 功能:增加或减少 WaitGroup 的计数器;
  • 参数 delta 可正(增加计数)可负(减少计数);
  • 通常在启动新的 goroutine 之前调用 Add(1)

(2)Done()

  • 功能:WaitGroup 的计数器减一;
  • 等价于Add(-1)
  • 常在 goroutine 完成时调用 Done()

(3)Wait()

  • 功能:阻塞当前 goroutine,直到 WaitGroup 的计数器变为 0;
  • 通常在 main goroutine 调用,阻塞地等待所有子 goroutine 完成。

注意事项

(1)计数器的初始值:
计数器的初始值必须为 0,如果是负数将触发 panic;

(2)AddDone 的调用顺序:

  • Add 必须在启动 goroutine 之前调用;
  • Done 必须在 goroutine 完成后调用,通常使用 defer 确保调用;

(3)WaitGroup 值传递:
WaitGroup 是值类型,传递时应使用指针(如 &wg),否则会导致计数器无法正确更新。

(4)避免竞争条件:
如果多个 goroutine 同时修改 WaitGroup 的计数器,可能会导致竞争条件。可以使用 sync.Mutexsync/atomic 包来保护计数器。

使用示例

package mainimport ("fmt""sync""time"
)func main() {var wg sync.WaitGroupfor i := 1; i <= 3; i++ {wg.Add(1) // 增加计数器go worker(i, &wg)}wg.Wait() // 阻塞,直到计数器为 0fmt.Println("所有 goroutine 完成")
}func worker(id int, wg *sync.WaitGroup) {defer wg.Done() // goroutine 完成时减少计数器fmt.Printf("Worker %d 开始工作\n", id)time.Sleep(time.Second * time.Duration(id)) // 模拟工作耗时fmt.Printf("Worker %d 完成工作\n", id)
}

与 channel 对比

  • WaitGroup:适合简单的等待场景,代码更简洁。
  • channel:适合需要 goroutine 之间通信的场景,功能更强大但代码更复杂。
http://www.dtcms.com/wzjs/363897.html

相关文章:

  • 邯郸做wap网站找谁网站推广及seo方案
  • 诚信通开了网站谁给做中国十大企业培训公司
  • 沈阳网站制作优化实训百度搜索引擎的总结
  • 网站cc攻击用什么来做搜索引擎优化seo多少钱
  • 临沂做wish网站佛山全市核酸检测
  • 怎么做网站推广多少钱百度推广关键词排名规则
  • 自已创建网站要怎么做2345网址导航官网官方电脑版
  • 哈尔滨网站开发制作seo初级入门教程
  • 自己做网站实时监控如何优化推广中的关键词
  • 最棒的网站建设广告精准推广平台
  • 网站原型的交互怎么做磁力兔子
  • 如何将网站的关键词排名优化网盘资源大全
  • 做网站方案怎么写怎样打开网站
  • 咸阳北京网站建设抖音关键词推广
  • 什么网站可以注册微信支付方式男生最喜欢的浏览器
  • 做简单网站的步骤百度搜索app下载
  • b2c跨境电子商务平台有哪些?百度seo网络营销书
  • 网络营销与网络推广的异同seo推广员是做什么的
  • 网站建设外包公司容易被客户投诉吗百度一下官方入口
  • 广安市建设局官方网站站长工具seo推广秒收录
  • 公众号做网站百度链接提交入口
  • 网站模板分享网络优化工程师骗局
  • 电商网站产品模块苏州网站优化排名推广
  • 知名网站制作公友情链接有哪些
  • 网站建设上线多久网站的搜索引擎
  • 一小时做网站狼雨的seo教程
  • 福建建设培训中心网站长沙哪里有网站推广优化
  • 医美的网站主页怎么做如何被百度收录
  • 安徽省政府网站官网引流推广营销
  • 购物网站开发流程网上宣传广告怎么做