由于 go 是一种并发程序语言并使用channel
(我几乎所有代码都使用它)或其他东西来同步goroutine
。
而且我也知道 go 使用调度器来调度goroutine
,这意味着你应该scheduler
在每个 goroutine 中调用(通道操作、runtime.goSche 或其他东西)并保证它会被执行。
以上是我目前的所有限制go
,我用它们来设计我的代码。
但我也发现它会在我的代码中发生代码阻塞。而且很难找到导致阻塞的原因(甚至使用GDB)。
我错过了什么吗?
还有什么可能导致阻塞?
我应该注意什么?
[编辑]:好的,因为我的项目的代码有点大。我决定不显示标准 go
代码,只显示可能导致代码阻塞的部分的一般概念。以下是我项目中文件传输模块的一些代码片段:
func(c *client)listenRead() {
for {
_ := websocket.JSON.Receive(c.ws, &package)
switch package.Op {
case fileUpld:
c.fileMan.store <- package.Body
case fileDownld:
c.fileMan.downld <- package.Body
case c.xxx:
...
default:
// bad package.
}
}
}
一个客户端通过websocket从另一个客户端监听和接收消息,然后根据package
'Op
字段将包转发到不同的处理程序,例如,fileManager
如下所示。
func(fm *fileManager) fileRouter() {
for {
select {
case fs := <-fm.fileUpld:
if window < filesize {
f.Write(fs.content) // client A write to file
window += fs.contSize
} else {
f.close() // close file
}
case fd := fm.downld:
go fm.downldFile(fd)
}
}
}
当chan获取从 发送的数据时, AfileManger
可以接收文件片段并存储到服务器。当它收到来自客户端的文件时,它可以下载文件,它会生成一个来完成这项工作,如下所示: fileUpld
client
request
goroutine
func(fm *fileManager) downldFile(fd) {
f := getFile(fd) // get the file that client A write
b := make([]byte, SeqLength)
for {
if convergence < window {
f.Read(b)
// wrap 'b' to package 'p', for send
fm.server.send <- p // send to client B
} else if window < fileSize {
runtime.Goshed()
} else {
// download done.
fm.done <- xx
return
}
}
}
我的文件传输模块的主要思想是客户端 A想要向客户端 B发送文件,并且不想等待acceptance
来自客户端 B,这意味着它将首先存储到服务器,客户端 B稍后将通过从服务器下载。但是客户端 B可能会在客户端 A上传文件时下载文件。所以上传和下载之间需要同步。
我使用三个变量来实现这一点:window,convergence,filesize。当客户端A上传(写入)文件时,窗口将增加它写入的字节的值。当客户端 B下载(读取)文件时,收敛会增加它读取的字节数。Write
仅发生window < filesize
而read
仅发生convergence < window
。就像:
+-----------+---------------+--------------+
|############|||||||||||||||| |
+-----------+---------------+--------------+
^ ^ ^
| | |
convergence window filesize
我知道这会导致数据竞争,但它也保证read
不会读取write
尚未写入的内容。(顺便说一句:我没有channel
用于实现它的解决方案。)
当我尝试同时上传和下载时会发生代码阻塞(我减慢了客户端 A 的速度来实现它)。GOMAXPROCS
但是当我设置为2时它没有问题。