V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
V2EX  ›  xuantedev  ›  全部回复第 1 页 / 共 1 页
回复总数  16
@2i2Re2PLMaDnghL 如果是这样的话,一个 repo 对应一个 project,那 bitbucket 应该不用提出 project 这个概念了啊。
@2i2Re2PLMaDnghL 谢谢,为什么我要从一个路径来判断这是一个 project 还是一个 repo 呢?绝大多数 project 只有一个 repo,我本身不是很赞同这个说法。何况这只是一个组织方式,一个 project 中只有一个代码库也许情况比较多,但是 document 呢?

@wangkun025 谢谢,但是我还是不理解我这个问题跟 auth token 有什么关系。
@lujjjh 太感谢了,这应该是正确的做法了。前面看了很多代码片段,都是把 Stop()放在 select 之外的,头脑禁锢了。感谢感谢~
@lujjjh 感谢细心回复。

现在的问题是,在消费 channel 中的内容的时候,您是选择阻塞消耗,还是尝试消耗,这两者只能选择一个。

阻塞消耗,如果 f 没有调用,就会阻塞:
```go
select {
case <-timer.C:
xxx
}
```
尝试性消耗,即使 f 没有调用,也不会阻塞:
```go
select {
case <-timer.C
xxxx
default:
xxxx
```

如果选择阻塞性消耗,就是您编写的代码情况,那么假定的前题条件是:Stop()返回 False 表明一定执行了 f 。但是在某些情况下,不能做这样的保证,可以参考这个 bug:
```
https://github.com/golang/go/issues/14038
```

如果 Stop()返回了 False,但是没有执行 f,那么上面的阻塞式读取就会出现问题。我们再看看什么场景下会出现 Stop()返回 false,但是 f 没有执行的情况。

只要前面超时过(第一次发送了 channel 数据),然后您的程序从正常逻辑(不是 drain channel 那段)中读取了数据,Channel 会为空。此时您去 Stop(),肯定返回 False,您再去阻塞读取 Channel,那么就会阻塞。

您可以拿代码运行一下看看:第一个匿名函数是生产者,第二个匿名函数是正常的业务逻辑(即消费者)。
正常情况下,读取数据的超时值是 2 秒。
第一次读取就超时,因为生产者 3 秒后才生产数据,超时后,BBB 处的代码会把 channel 读取干净,这也很正常,超时事件发送给我们,我们消耗它。
循环第二次执行,Stop()会返回 False (因为定时器已经 expired 了),但是不会调用 f 去 sendTime,这也就是我前面说过的超时和 f 的调用是两件独立的事情,Stop()的返回值不会反应是否发送 sendTime 。
此时,再 AAA 处的阻塞调用,就会阻塞。因为 Channel 里面的确没有数据。

```go
package main

import (
"fmt"
"time"
)

func main() {
c := make(chan bool)
go func() {
for i := 0; i < 10; i++ {
time.Sleep(time.Second * 3)
c <- false
}
}()

go func() {
t := time.NewTimer(time.Second * 2)
for {
if !t.Stop() {
<-t.C // AAA: will blocked here
}
t.Reset(time.Second * 2)
select {
case b := <-c:
if !b {
fmt.Println(time.Now(), "work...")
continue
}
case <-t.C: // BBB: normal receive from channel timeout event
fmt.Println(time.Now(), "timeout")
continue
}
}
}()

time.Sleep(time.Second * 10000000)
}
```

如果改成非阻塞性读取,那么又有我第一次说的那个多了 f 调用的问题。
@lujjjh 感谢兄弟的细心阅读,并且一眼就看出了我的担心,握爪。是这样的,其实问题就消费 Channel 这里。
有一种极端的情况,就是我消费 Channel 的时候,runtime 的 deltimer()还没有来得及调用 sendTime 。但是当我的消费代码走完了以后,它调用了 sendTime,导致了 Channel 里面又有了内容,这样,我下次正儿八经的业务 select 的时候,立马就会返回,导致我错误的认为超时了。

这个问题的出在 Runtime 中 timer 的维护 routine 中调用定时器处理函数 runOneTimer()。该函数的处理分为两部分:
* A) 从 timer 堆中处理定时器状态这部分
* B) 调用 f(arg, seq)来 sentTime 到 Channel 中
从 [代码]( https://github.com/golang/go/blob/891547e2d4bc2a23973e2c9f972ce69b2b48478e/src/runtime/time.go#L818)看,这两个部分操作没有放在一个锁中,因此不是原子操作。
```go
func runOneTimer(pp *p, t *timer, now int64) {
.......
if raceenabled {
// Temporarily use the current P's racectx for g0.
gp := getg()
if gp.racectx != 0 {
throw("runOneTimer: unexpected racectx")
}
gp.racectx = gp.m.p.ptr().timerRaceCtx
}

unlock(&pp.timersLock)

f(arg, seq) // 就是那个 sendTime 函数

lock(&pp.timersLock)

.......
}
```

设想在用户调用 Stop()的时候,虽然和 runOneTimer()的 A 部分产生了互斥,但是和 sentTime 并没有产生互斥。极端情况下,如果 runtime 的 routine 中把 A 部分执行完了,碰到 CPU 繁忙或者系统调度等极端场景,B 部分一直没有机会执行。那么 User 的 Stop()、消费 Channel (实际上没有数据)、然后 Reset(),到这里 B 部分都没有执行。

这样,Channel 中一直没有数据,但是当用户调用业务 Select()的时候,runtime routine 中的 B 部分执行了,sendTime 将数据发送到了 Channel 中。select()就因为上一次 Timer 的数据而触发了。

这也就是我在问题中说的,`(理论上)一定概率的立即超时`。


其实 Golang 的相关模块作者也注意到了该问题,修改的次数非常多,提供的注释也非常长。


其中作者提到,Stop()并不会等到 f (即 sentTime)执行完毕后才返回,也就是说 Stop()只反馈 Timer 当时的状态,不反馈 channel 的状态。
```
// Stop prevents the Timer from firing.
// It returns true if the call stops the timer, false if the timer has already
// expired or been stopped.
// Stop does not close the channel, to prevent a read from the channel succeeding
// incorrectly.
//
// To ensure the channel is empty after a call to Stop, check the
// return value and drain the channel.
// For example, assuming the program has not received from t.C already:
//
// if !t.Stop() {
// <-t.C
// }
//
// This cannot be done concurrent to other receives from the Timer's
// channel or other calls to the Timer's Stop method.
//
// For a timer created with AfterFunc(d, f), if t.Stop returns false, then the timer
// has already expired and the function f has been started in its own goroutine;
// Stop does not wait for f to complete before returning.
// If the caller needs to know whether f is completed, it must coordinate
// with f explicitly.
```

而对于 Reset(),作者也是用了大量篇幅,指出 Reset 的返回值并不是准确的,因为竟态导致的问题。提供返回值只是为了兼容过去的接口。另外其实 time 模块的 Reset()调用的其实就是 runtime 的 resetTimer,而 resetTimer()其实就是内部调用 stopTimer()。
```
// Reset changes the timer to expire after duration d.
// It returns true if the timer had been active, false if the timer had
// expired or been stopped.
//
// For a Timer created with NewTimer, Reset should be invoked only on
// stopped or expired timers with drained channels.
//
// If a program has already received a value from t.C, the timer is known
// to have expired and the channel drained, so t.Reset can be used directly.
// If a program has not yet received a value from t.C, however,
// the timer must be stopped and—if Stop reports that the timer expired
// before being stopped—the channel explicitly drained:
//
// if !t.Stop() {
// <-t.C
// }
// t.Reset(d)
//
// This should not be done concurrent to other receives from the Timer's
// channel.
//
// Note that it is not possible to use Reset's return value correctly, as there
// is a race condition between draining the channel and the new timer expiring.
// Reset should always be invoked on stopped or expired channels, as described above.
// The return value exists to preserve compatibility with existing programs.
//
// For a Timer created with AfterFunc(d, f), Reset either reschedules
// when f will run, in which case Reset returns true, or schedules f
// to run again, in which case it returns false.
// When Reset returns false, Reset neither waits for the prior f to
// complete before returning nor does it guarantee that the subsequent
// goroutine running f does not run concurrently with the prior
// one. If the caller needs to know whether the prior execution of
// f is completed, it must coordinate with f explicitly.
```

所以从这些场景看,很难精确的判断出来 Channel 是否有数据,而尝试去 Drain Channel 也只是大概率可以 drain out,极端情况下,drain empty channel 之后,还会立即有数据 send 进来。
@lesismal 感谢大神赐教,受益匪浅。
347 天前
回复了 xuantedev 创建的主题 Android 安利一个 Android(AOSP)的源代码交叉索引
@richard1122 界面的区别哈,这个用 opengrok 的界面,列出来的条目比较清晰,可以指定子工程和目录,看不同的习惯,好多老同学喜欢这个:)
能看源代码的都是一堆事情要去做的主,视频这种信息密度太低的媒体不适合他们,偶尔看一下也许有可能,但是很难形成习惯。休闲的、打法时间的视频才吸引人。
2020-11-01 12:53:32 +08:00
回复了 Vneix 创建的主题 分享发现 把很多年前的一台配置极低上网本利用了起来,感觉还不错
@WebKit 100 多电费楼主的机器也可以用很久吧。
2020-10-28 20:53:23 +08:00
回复了 xuantedev 创建的主题 git 感觉最近几个月来大的 git 库很难下载成功了
我一般从 tuna mirror 上下载比较大的 git 库。
2020-10-28 20:52:19 +08:00
回复了 xuantedev 创建的主题 git 感觉最近几个月来大的 git 库很难下载成功了
谢谢大家,我这儿是指 git 库,不一定是 google source 或者说 github 。不是访问不了,其实我觉得问题还是出在 https 上,不让太长时间处于连接。
@mangogeek 谢谢,我主要是想看看比如某个平台,比如 Qualcomm 平台的 msm 系列、或者飞思卡尔的实现大概在哪些文件中,然后参考下。
2020-10-19 17:18:45 +08:00
回复了 xuantedev 创建的主题 Android 一直墙内可以用的 g.cn/generate_204 终于挂了
今天又可以用了,谢谢大家的帮助~
2020-10-19 17:18:10 +08:00
回复了 wvtjplh 创建的主题 分享创造 写了一个电子发票合并打印的工具,请大家捧场
@howellz 是的,最初用这个名字的是这个网站 https://fapiao.bangnimang.net ,后来楼主也做了个类似的。其实再做一个本来没啥,关键是用别家名字就不厚道了。
2020-10-18 16:08:16 +08:00
回复了 wvtjplh 创建的主题 分享创造 写了一个电子发票合并打印的工具,请大家捧场
@Muniesa 呵呵,楼主做个这个不错,但是连人家的名字都抄袭,就有点不厚道了。
2020-10-18 16:07:02 +08:00
回复了 wvtjplh 创建的主题 分享创造 写了一个电子发票合并打印的工具,请大家捧场
楼主,虽然做一个这样的工具每个人都可以做,但是把人家的名字直接拿到自己的主页作关键词就有点不厚道了。
这里都是搞技术的,你做得好,大家都会捧场,但是这种直接挂人家羊头的方式太不值得。
建议把主页的标题中别人的作品名去掉。
这里是人家的作品名字:
https://img-blog.csdnimg.cn/20190916161941563.png
关于   ·   帮助文档   ·   API   ·   FAQ   ·   我们的愿景   ·   广告投放   ·   感谢   ·   实用小工具   ·   1128 人在线   最高记录 5497   ·     Select Language
创意工作者们的社区
World is powered by solitude
VERSION: 3.9.8.5 · 27ms · UTC 23:43 · PVG 07:43 · LAX 15:43 · JFK 18:43
♥ Do have faith in what you're doing.