Golang中Channel未按预期工作的问题排查

Golang中Channel未按预期工作的问题排查 本地图形用户界面上有多个添加和删除按钮。一旦我从图形用户界面点击添加/删除按钮,就会通过“mainHandler”处理器以数组形式获取到活动按钮的ID。

然后,我从数组中提取每个ID并进行一些处理。对于每个ID,处理应定期(例如每60秒)持续进行。这将无限期地持续下去。

一旦我再次点击添加/删除按钮,我将再次获取所有活动ID的完整列表,并且只需要对这些ID进行处理。但有时,当我从本地图形用户界面添加/删除列表时,主线程没有被调用。

如果我注释掉 go c.func1() 函数,那么每当从本地图形用户界面更新数据时,我都能在处理器中接收到数据。

代码片段:

type DataTable struct {
Exit     chan struct{}
DataList []string
}

var dataTable *DataTable
func (c *controller) mainHandler(mc xxxx) {
if dataTable == nil {
dataTable = &DataTable{}
} else {
dataTable.Exit <- struct{}{}
}
r := mc.BodyReader()
dataTable.DataList = r.GetSliceOfString("data") // 在切片中更新ID
go c.func1()
}

func (c *controller) func1() {
for {
select {
case <-time.After(60 * time.Second):
c.func2()                           // 处理将在该函数内对 dataTable.DataList 进行。有时此函数需要10秒来完成操作。
case <-dataTable.Exit:
return
}
}
}

更多关于Golang中Channel未按预期工作的问题排查的实战教程也可以访问 https://www.itying.com/category-94-b0.html

7 回复

是的。谢谢

更多关于Golang中Channel未按预期工作的问题排查的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


你必须以 goroutine 的方式调用测试函数。否则程序会进入无限循环。

go testFunc() 有效吗?

请查看以下代码:

https://play.golang.org/p/t_lCaOIxKRs

当我发送 quit <- true 信号时,goroutine 应该立即停止,但它并没有停止。

如果程序中有任何错误,请告诉我。

这个用例的目的是立即停止 goroutine。

我认为“Go语言之旅”中的这段代码展示了@Gowtham_Girithar@ani所说的关于go testFunc()的内容。

select语句会阻塞,直到其某个case可以运行,然后执行该case。如果有多个case准备就绪,它会随机选择一个。

https://tour.golang.org/concurrency/5

处理将在此函数内的 dataTable.DataList 上进行。有时此函数需要 10 秒来完成操作。

不清楚我们是否在讨论“网页表格”。我使用 ag-Grid 而不是 dataTable。

如果我们讨论的是数据表格,我发现在这种情况下涉及 Go 会使很多事情变慢。我决定使用 AJAX 而不是 Go。使用 CSR 而不是 SSR。

https://form.go4webdev.org/aggrid

问题出在通道 dataTable.Exit 的初始化时机和缓冲处理上。当 dataTable 首次创建时,Exit 通道未被初始化,导致后续向该通道发送数据时阻塞。同时,非缓冲通道在接收者未准备好的情况下会导致发送操作阻塞,这可能影响主线程的调用。

以下是修改后的代码:

type DataTable struct {
    Exit     chan struct{}
    DataList []string
}

var dataTable *DataTable

func (c *controller) mainHandler(mc xxxx) {
    if dataTable == nil {
        dataTable = &DataTable{
            Exit: make(chan struct{}), // 初始化通道
        }
    } else {
        select {
        case dataTable.Exit <- struct{}{}:
            // 成功发送退出信号
        default:
            // 避免阻塞,丢弃多余的退出信号
        }
    }
    
    r := mc.BodyReader()
    dataTable.DataList = r.GetSliceOfString("data")
    go c.func1()
}

func (c *controller) func1() {
    defer func() {
        // 清理资源
        if dataTable != nil {
            close(dataTable.Exit)
            dataTable = nil
        }
    }()
    
    for {
        select {
        case <-time.After(60 * time.Second):
            c.func2() // 处理 dataTable.DataList
        case <-dataTable.Exit:
            return
        }
    }
}

关键修改点:

  1. 在创建 DataTable 时初始化 Exit 通道
  2. 使用 select 语句配合 default 分支避免通道发送操作阻塞
  3. func1 返回时关闭通道并清理资源

如果 func2 执行时间较长(如10秒),考虑使用带缓冲的通道并优化处理逻辑:

type DataTable struct {
    Exit     chan struct{}
    DataList []string
    mu       sync.RWMutex // 添加互斥锁保护 DataList
}

func (c *controller) func1() {
    ticker := time.NewTicker(60 * time.Second)
    defer func() {
        ticker.Stop()
        close(dataTable.Exit)
        dataTable = nil
    }()
    
    for {
        select {
        case <-ticker.C:
            dataTable.mu.RLock()
            list := make([]string, len(dataTable.DataList))
            copy(list, dataTable.DataList)
            dataTable.mu.RUnlock()
            
            go c.func2(list) // 异步处理副本,避免阻塞定时器
        case <-dataTable.Exit:
            return
        }
    }
}

这样确保:

  • 通道操作不会阻塞主线程
  • 数据访问是线程安全的
  • 长时间处理不会影响定时逻辑
回到顶部