V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
V2EX  ›  volvo007  ›  全部回复第 23 页 / 共 51 页
回复总数  1007
1 ... 19  20  21  22  23  24  25  26  27  28 ... 51  
2022-11-07 14:16:34 +08:00
回复了 volvo007 创建的主题 Go 编程语言 小白学完 channel 马上就不会了 —— 现在会了(大概)
@zjj19950716 嗯嗯,好的,是叫 『等待组』吗?
2022-11-07 12:15:59 +08:00
回复了 volvo007 创建的主题 Go 编程语言 都说 go 简单 小白学完 channel 马上就不会了
这篇主题发表于 180 天前。我也去爆栈问过,但是回答都和我这样操作文件不安全相关,并没有正面解决这个问题。

期间我去和单片机嗑了一段时间,当然还有各种本职工作,关于 goroutine 、channel 、sync 库 之类的东西没有顾得上。前几周工作稍微松了一点,这个话题又在心里浮现出来,总觉得是个坎,不彻底搞清楚的话自己这关过不去,也别谈掌握 golang 了。另外,我还注意到这个话题不断有零星的用户在关注,又让我感觉自己好像是个逃兵,问题还没解决我就跑了……

言之种种,在我做了一些针对 channel 的练习之后,算是大概搞清楚了这个例子要怎么写。可能不是 best practice ,但希望能帮到大家,特别是一直关注这个帖子的朋友。

========
在开始之前,我们稍微回顾一下之前的逻辑。相关函数见主题帖子的最后一部分

抽象一下这个主题的使用场景:
函数 1 是一个简单、耗时很短的功能;函数 2 则是一个复杂、耗时长的功能。
我们期待通过 goroutine 来并发处理 函数 2 以达到提升处理性能的目的。

我用的文件处理案例,根据大佬们所说会有线程安全问题,我们先忽略这个问题,主要还是把握后面的方法论哈

1. 函数 getInfo (f []fs.FileInfo, c chan<- string) 通过遍历 []fs.FileInfo 结构,进行了一些简单判断后(例如忽略文件夹、.DS_store 这种),不断将文件名写入 c 这个 string channel 中

2. 函数 dealInfo (path string, typeDict map[string]int, c <-chan string) 通过 range 方法,不断获取 c 之前保存的文件名,截取后缀之后,要么转入对应文件夹,要么创建新文件夹再转入

========
到这里其实思路上是没有什么问题的,这里最关键的是没有注意到简单练习里不会提到的一个知识点:**用 range 遍历 channel 的时候,需要主动 close channel. 否则 range 会阻塞 channel 直到 deadlock panic**. 尽管所有 channel 会在 main channel 结束的时候被强制结束.

如果不用 range 的方式来遍历的话,我们需要写一个 if _, ok := <- c; ok { ... } 这样的东西放倒一个死循环里面,也就是每次循环都要来手动判断一次 c 里面还有没有东西,没东西了我就跳出循环呗。显然 range 遍历的方式更优雅,但要考虑 channel close 的时机。

第二个点则是如何 “并发” 处理 函数 2 。如果只用 go func(),最多只能实现两个 goroutine 之间的通信,所以我们引入了线程池 sync 库来解决这个问题——我们需要给每个 goroutine 加入到线程池里面,但在某个线程工作结束的时候又要把它从池子里面拿掉。最后,还需要一个 wait 函数来通知主线程等待这些线程工作结束。

具体来说,我们需要改写一下前面的函数 1 、函数 2 了:

对于函数 1 ,原始伪代码:
func getInfo(f []fs.FileInfo, c chan<- string){
遍历 f { 处理后的 fineName 写入 c }
}

现在应当改写为:
func getInfo(f []fs.FileInfo, c chan<- string){
// 后面要用 sync.Add 加入池子,所以这里要减去。加入和减去要匹配, 重要!
defer sync.Done()

遍历 f { 处理后的 fineName 写入 c }

// 后面其他函数会用 range 来遍历,所以一定要 close ,重要!
close(c)
}

对于函数 2 ,由于会用多个 goroutine 并发,那么每一次都需要一个 sync.Add(1) 来加入,所以每一次我们还要从 函数 2 里减去这个线程

原函数 2 伪代码:
func dealInfo(path string, typeDict map[string]int, c <-chan string){
for _, filename := range c {
判断文件;
处理文件;
}
}

现在改写为:
func dealInfo(path string, typeDict map[string]int, c <-chan string){
defer sync.Done()

for _, filename := range c {
判断文件;
处理文件;
}
}

非常简单,就是在循环前加一个 defer sync.Done() 就可以了。

最后,我们来写主函数的伪代码:

<====

import ("sync", ... )

var wg sync.WaitGroup // 为了创建多线程并发,准备线程池

func getInfo( ... ) // 实现 func1

func dealInfo( ... ) // 实现 func2

func main(){

c := make(chan string, 1000)

wg.Add(1)
go 函数 1

for i:=0; i<16; i++ {
wg.Add(1)
go 函数 2
}

wg.Wait()
}

====>

这里应该就能充分暴露前面改写过程中加入的奇怪东西的目的了 😄

可以发现,wg.Add(1) 之后,一定会紧跟一个带有 defer wg.Done() 的函数,来实现线程加减的匹配

而对于比较复杂的函数 2 ,我们通过一个循环来加入 N 个 goroutine 线程。wg.Add(1) 放在循环里面,同时每个 wg.Add() 都必然对应一个 defer wg.Done() 来匹配

最后,别忘了放一个 wg.Wait() 来通知主线程等待所有 wg 的线程执行完毕——它靠的就是不断 Add ,之后又不断 Done ,直到池子里线程归零的那一瞬来判断任务全部结束的。所以 Add 和 Done 必须匹配

另外一个之前没有提到的小改动是,我们建立 c (chan string) 的时候,还给了它一些缓存。这样,由于 func1 处理得很快,就可以预存一些结果到 c 里面,在面对 16 个 go func2 的时候,就能保证每个 func2 总是能拿到东西来处理,就不会空闲等待了。这个 N ,我在哪看到资料说是最大 10000 个,好像可以通过配置修改。不过对于大部分的场景,如果要修改这个参数,不如优化代码才是正道

还有一个地方是,我们在循环加入 函数 2 goroutine 的时候,wg.Add(1) 放在了循环里面。由于我知道这里的循环会创建 16 个 goroutine ,所以我们也可以一开始就在循环外面 wg.Add(16) 把它一口气全加进去。由于每个循环有一个 defer wg.Done() ,所以最后线程池还是可以归零的。只是这样写如果后期要扩充数量的话会有点不好维护,还是每个循环 +1 ,N 则通过配置文件来提供更妥当。
2022-11-06 17:40:27 +08:00
回复了 xt1990xt19900 创建的主题 Apple ventura 建议升级吗
@gilgameshhh 内录这么重要的功能……
2022-08-12 11:57:31 +08:00
回复了 terryl 创建的主题 iOS iPhone11 在升级了最新的 iOS16 Public Beta 以后,耗电令我担忧。
谢谢大佬们趟雷……
第一个 beta 因为发热回退了,还丢了健康记录
没想到到第五个 beta 了还这样
2022-07-07 16:20:19 +08:00
回复了 kxxoling 创建的主题 Apple 完全无法理解🍎这个快捷设置的意义
@starrystarry 我说怎么 UI 和图标变成这个鬼样子……
因为发热所以回 iOS15 了
2022-06-26 22:02:21 +08:00
回复了 janeedel 创建的主题 Apple beta2 终于来了
看样子生产主力机都不能上了。beta1 已经让我滚回 15 还掉了一堆东西
2022-06-26 21:54:50 +08:00
回复了 Lionad 创建的主题 Apple 我悟了,折腾什么桌面,最简洁的就是直接放一个 MacBook
我想到一个说法:差生文具多😂
就是个梗,不针对任何事情
2022-06-22 19:41:46 +08:00
回复了 MID 创建的主题 MacBook Pro m1 max 除了用来剪片,还能用来干什么?
全干工程师最近突然要用 blender 了,发现 pro 有点不够用……
2022-06-22 19:39:31 +08:00
回复了 makeitwork 创建的主题 MacBook Pro m1 max 用啥都快,就是辣鸡 office 还是那么卡顿
有没有可能是正版软件受害者
比如保存的时候会访问云端,尽管不保存到云端但还是要访问一下这样……
2022-06-17 10:03:45 +08:00
回复了 jayce789 创建的主题 Apple MAC 电脑两个触控板分别控制两个屏幕的一些思考
@yyfearth 妙啊, 单位的工作站有救了!
2022-06-16 20:59:53 +08:00
回复了 YoungKing6 创建的主题 Apple 618 MBP 外接键盘推荐
mac 键盘的精髓在于偏短的 space 以及两侧的 cmd
很多键盘说是兼容 Mac ,但是 cmd 的位置真是太难用了……
2022-06-16 11:38:09 +08:00
回复了 jayce789 创建的主题 Apple MAC 电脑两个触控板分别控制两个屏幕的一些思考
@yyfearth 愿闻其详,这个要咋操作。单位的工作站想利用一下
2022-06-15 11:07:25 +08:00
回复了 zhouwb 创建的主题 Apple Mac 上的 docker 怎么让容器 ip 与宿主同一网段
@q1angch0u 多谢大佬, 我也很苦恼 docker desktop 占用资源有点多的. 我也不跑啥大的东西
2022-06-14 07:52:59 +08:00
回复了 GinXgo 创建的主题 iPhone iOS 16 保资料降级 15.5,之前没备份也不用担心
我也滚回去算了……太烫了
2022-06-08 20:42:19 +08:00
回复了 unt 创建的主题 Apple mac 用磁铁模拟合盖熄屏
1. 现在是霍尔传感器, 可以检测到盖子的开合角度, 所以光靠磁铁是不行了
2. 出风, 进风在机器两侧
2022-06-08 01:13:00 +08:00
回复了 StevenRCE0 创建的主题 Apple macOS 的偏好设置更新设计了
反正平时都是直接搜,易用性上面对我没啥影响。图标换了我觉得大快人心,虽然没有早期的经典,但是现在的都是什么玩意……那个 desktop 没有过渡的方框,电池图标像个初中生做的,还有 mission control 那个绿色+紫色的搭配…… 我真怀疑这图标 implement 团队都是三哥…… 大绿、紫色,都是他们爱用的……
2022-05-31 06:36:19 +08:00
回复了 starryloki 创建的主题 Apple Windows 与 macOS 上的 RAMDisk 速度差异巨大
@starryloki 就 spotlight 里面可以设置不加入索引扫描的文件夹,把这个内存盘加进去试试?虽然我确实也怀疑这么做没啥用,就死马当活马医吧
2022-05-30 21:21:44 +08:00
回复了 oscedslvhe 创建的主题 问与答 打印机打印无表格边框
excel 和 word 默认是不加框线的啊
你可以选中 excel 任意一个区域然后填充一种颜色,会发现填充区域框线都不见了。因为默认的是辅助线不是实体框线
1 ... 19  20  21  22  23  24  25  26  27  28 ... 51  
关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   965 人在线   最高记录 6679   ·     Select Language
创意工作者们的社区
World is powered by solitude
VERSION: 3.9.8.5 · 33ms · UTC 21:12 · PVG 05:12 · LAX 13:12 · JFK 16:12
Developed with CodeLauncher
♥ Do have faith in what you're doing.