Golang中Ticker会创建新实例还是等待现有实例完成?

Golang中Ticker会创建新实例还是等待现有实例完成? 以下是我的代码:Go Playground - Go 编程语言。它应该每10秒从MySQL的一个暂存表中读取数据。如果有数据,我会运行一些逻辑,这些逻辑涉及将所有操作放入一个事务中。我的困惑是,假设一个实例已经在运行,并且它花费了超过10秒的时间仍未完成,那么下一个实例是会开始运行,还是会等到前一个实例完成后再启动?

17 回复

是的。 如果同步过程耗时超过10秒,你将错过一个tick。

更多关于Golang中Ticker会创建新实例还是等待现有实例完成?的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


你好,

你需要决定这是一个同步还是异步过程,因为我不完全清楚你想要做什么。

不,你需要一个处理协程来从通道读取工作请求。如果只有一个协程处理所有任务,你将无法实现请求的并发处理。

你好 Jeff,

根据我最近的代码,我有点迷茫,你能根据你的建议给我一些提示,告诉我需要更改或编辑什么吗?

嗨,Jeff,

既然我需要它是同步的而不是异步的,那么我在调用我的函数时不应该加上 go 这个词吗?

你好,Ermanimer,

它应该是同步的,因为我不希望在前一个实例完成之前启动任何新实例。那么你的建议是不要在调用时使用 go 关键字,对吗?

是的,这正是我期望的方式,因为如果启动另一个线程,它可能会与数据库更新操作重叠。所以现在我对Go语言中的正确做法感到困惑,即如果操作耗时超过10秒,它是否会错过执行。

嗨 Mje,

我已经按照你提到的做了一些修改来保持执行状态。这是我的代码修改版本 Go Playground - The Go Programming Language。这是你想要的吗?所以,如果它仍在运行并且状态为真,那么它就直接返回吗?

newbiegolang:

make(chan struct{})

你可能希望这是一个带缓冲的通道,这样发送请求的 goroutine 就不会阻塞。在目前这种主例程发送前等待完成通道消息的方式下,它可能不会阻塞,但以防万一。

newbiegolang:

_ := range requestCh

去掉冒号。

你可能需要一个goroutine来处理你的工作,它从一个通道接收工作请求,并在另一个通道上通知完成。你可以使用一个循环,在定时器和通知通道之间使用select,这样一旦定时器触发并且收到前一个工作完成的通知,它就会发送更多工作。这将确保一次只处理一个工作请求,并且不会比超时时间更频繁地运行。

func main() {
    fmt.Println("hello world")
}

你好 Jeff,

如何决定是否使用缓冲区?其次,我其实不太理解 _ = range requestCh 这个逻辑。因为这个 requestCh 是空的,对吧?那它怎么循环呢?它的起始值和结束值是什么?这是最新的代码,没有错误 Go Playground - The Go Programming Language,但现在它显示“运行程序超时”。

func main() {
    fmt.Println("hello world")
}

好的,我想进一步学习以理解它。

  1. Go语言之旅 中提到的缓冲区,在我看来,你似乎是构建了一个类似数组的东西,然后根据索引进行赋值,是这样吗?

  2. 基于这段代码 completeCh ← struct{}{},我想进一步理解 fmt.Println("struct{}{} ", struct{}{}) 我得到的结果是 {},这是否意味着 false?

  3. 为什么有些地方只用 struct{},而有些地方用 struct{}{}?根据我的阅读,它被称为空结构体?

你的所有问题都在这里得到了解答。

https://go.dev/tour/concurrency/2 https://go.dev/tour/concurrency/3 https://go.dev/tour/concurrency/4

completed 的初始值改为 true,并在经过一定次数的计时器触发后跳出循环,这样它就不会是无限循环,并且可以正常工作。

Go Playground

Go Playground - The Go Programming Language

我还缩短了计时器的间隔,让它运行得更快。

嗨 Jeff,

我已经尝试运行了你的代码,可以在这里查看 Go Playground - Go 编程语言。我现在很高兴,因为我继续学习了通道这个很好的概念,但我对它仍然很陌生。我在代码中修复了一些地方,其中之一是 requestChan := make(chan struct{}{}, somebuffersize),我把它改成了 requestChan := make(chan struct{})。我不得不移除第二个花括号以及 somebuffersize。现在我唯一卡住的地方是这里:for _ := range requestCh {,这个错误指的是 := 左侧没有新变量。

func main() {
    fmt.Println("hello world")
}

未经测试:

  requestChan := make(chan struct{}{}, somebuffersize)
  completedChan := make(chan struct{}{})
  go processor(requestChan, completedChan)
  
  ...
  ticked := false
  completed := false
  for {
    select {
      case <-tick.ticker.C:
        ticked = true
      case <- completeChan:
        completed = true
    }
    if ticked && completed {
      // 时间周期已过,处理器已准备好处理更多工作。
      // 这应该被简化,通过在一个无缓冲的 requestCh 上进行选择来省略 completedChan。
      // 我将把这留作一个更高级的练习。
      ticked=false
      completed=false
      requestChan <- struct{}{}
    }
  }
}
...

func processor(requestCh <-chan stuct[}{}, completeCh chan<- struct{}{}) {
   for _ := range requestCh {
      process()
      completCh <- struct{}{}
  }
}

在Golang中,time.Ticker会按照设定的时间间隔定期触发,不会等待前一个实例完成。每个间隔时间到达时,都会启动一个新的goroutine执行您的逻辑,无论前一个是否完成。

在您的代码中:

ticker := time.NewTicker(10 * time.Second)
for {
    select {
    case <-ticker.C:
        go func() {
            // 这里会每10秒启动一个新的goroutine
            // 即使前一个还在运行
        }()
    }
}

这会导致并发问题,特别是涉及数据库事务时。多个goroutine可能同时读取相同数据、处理相同记录,造成数据竞争或重复处理。

解决方案示例:使用互斥锁或通道控制并发

var mu sync.Mutex
ticker := time.NewTicker(10 * time.Second)
for {
    select {
    case <-ticker.C:
        go func() {
            mu.Lock()
            defer mu.Unlock()
            
            // 数据库事务逻辑
            tx, _ := db.Begin()
            // ... 处理数据
            tx.Commit()
        }()
    }
}

或者使用带缓冲的通道作为信号量:

sem := make(chan struct{}, 1) // 只允许一个并发
ticker := time.NewTicker(10 * time.Second)
for {
    select {
    case <-ticker.C:
        go func() {
            select {
            case sem <- struct{}{}:
                defer func() { <-sem }()
                // 数据库事务逻辑
            default:
                log.Println("前一个任务仍在运行,跳过本次执行")
            }
        }()
    }
}

如果业务要求严格按顺序执行(前一个完成后10秒再执行下一个),可以这样实现:

func runTask() {
    // 数据库事务逻辑
    time.Sleep(15 * time.Second) // 模拟长时间运行
}

func main() {
    for {
        runTask()
        time.Sleep(10 * time.Second) // 任务完成后等待10秒
    }
}

根据您的需求选择合适方案:如果任务必须串行执行,使用互斥锁;如果可以跳过某些执行,使用带超时的通道控制。

回到顶部