Go 语言用切片实现队列遇到无法理解的问题

2018-12-07 21:10:28 +08:00
 Licsber
package main

import (
	"fmt"
	"log"
	"net/http"
	"os"
	"time"
)

var Q []string

func main() {
	go func() {
		time.Sleep(1 * time.Second)
		for {
			test()
		}
	}()
	Q = append(Q, "233")
	Q = append(Q, "233")
	Q = append(Q, "233")

	time.Sleep(3 * time.Second)
	Q = append(Q, "233")
	Q = append(Q, "233")
	Q = append(Q, "233")
	Q = append(Q, "233")
	Q = append(Q, "233")

	time.Sleep(3 * time.Second)
	Q = append(Q, "233")
	time.Sleep(3 * time.Second)
	Q = append(Q, "233")

	http.HandleFunc("/", handle)
	err := http.ListenAndServe(":8080", nil)
	if err != nil {
		log.Println("http port has been used.")
		os.Exit(-1)
	}
}
func handle(w http.ResponseWriter, r *http.Request) {
	defer r.Body.Close()
	_ = r.ParseForm()
	_, _ = fmt.Fprint(w, "hello")

}
func test() {
	file := queueOut()
	if file != "" {
		log.Println(file)
	}

}

func queueOut() string {
	res := Q[0]
	if len(Q) == 1 {
		Q[0] = ""
		return res
	}
	Q = Q[1:]
	return res
}

我觉得我这样写没错啊. 但是运行的时候有几率出现

panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x1058fe5]

goroutine 19 [running]:
fmt.(*buffer).WriteString(...)
	/opt/local/lib/go/src/fmt/print.go:82
fmt.(*fmt).padString(0xc000114040, 0x0, 0x3)
	/opt/local/lib/go/src/fmt/format.go:110 +0x9d
fmt.(*fmt).fmtS(0xc000114040, 0x0, 0x3)
	/opt/local/lib/go/src/fmt/format.go:328 +0x61
fmt.(*pp).fmtString(0xc000114000, 0x0, 0x3, 0x76)
	/opt/local/lib/go/src/fmt/print.go:437 +0x122
fmt.(*pp).printArg(0xc000114000, 0x1247a20, 0xc000010050, 0x76)
	/opt/local/lib/go/src/fmt/print.go:671 +0x878
fmt.(*pp).doPrintln(0xc000114000, 0xc0000cffa8, 0x1, 0x1)
	/opt/local/lib/go/src/fmt/print.go:1146 +0xb1
fmt.Sprintln(0xc0000cffa8, 0x1, 0x1, 0x1247a20, 0x1)
	/opt/local/lib/go/src/fmt/print.go:271 +0x52
log.Println(0xc0000cffa8, 0x1, 0x1)
	/opt/local/lib/go/src/log/log.go:301 +0x3f
main.test()
	/Users/licsber/go/src/test/main.go:52 +0xd7
main.main.func1()
	/Users/licsber/go/src/test/main.go:17 +0x2f
created by main.main
	/Users/licsber/go/src/test/main.go:14 +0x39

Process finished with exit code 2

小白表示无法理解 这个问题是几率出现. 每次运行不一定都有

2722 次点击
所在节点    Go 编程语言
17 条回复
mlkr
2018-12-07 21:16:27 +08:00
要加锁吧
AlphaTr
2018-12-07 21:18:59 +08:00
slice 的线程安全问题,加锁?
Licsber
2018-12-07 21:24:11 +08:00
@mlkr
@AlphaTr 感谢 我去试试
mlkr
2018-12-07 21:26:07 +08:00
package main

import (
"fmt"
"log"
"net/http"
"os"
"sync"
"time"
)

var Q []string

var lock sync.RWMutex

func main() {
go func() {
time.Sleep(1 * time.Second)
for {
test()
}
}()
Q = append(Q, "233")
Q = append(Q, "233")
Q = append(Q, "233")

time.Sleep(3 * time.Second)
Q = append(Q, "233")
Q = append(Q, "233")
Q = append(Q, "233")
Q = append(Q, "233")
Q = append(Q, "233")

time.Sleep(3 * time.Second)
Q = append(Q, "233")
time.Sleep(3 * time.Second)
Q = append(Q, "233")

http.HandleFunc("/", handle)
err := http.ListenAndServe(":8080", nil)
if err != nil {
log.Println("http port has been used.")
os.Exit(-1)
}
}
func handle(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
_ = r.ParseForm()
_, _ = fmt.Fprint(w, "hello")

}
func test() {
file := queueOut()
if file != "" {
log.Println(file)
}

}

func queueOut() string {
// 加写锁
lock.Lock()
res := Q[0]
if len(Q) == 1 {
Q[0] = ""
return res
}
Q = Q[1:]
lock.Unlock()
return res
}
littlewing
2018-12-07 21:29:03 +08:00
有 channel 不用非要用非线程安全的 slice

另外 slice 的 append 操作是会重新分配 slice 结构的,而且你的 slice 初始 len 为 0,每次扩容时存储数据的 array 也会重新分配,问题应该就出现在这里
Licsber
2018-12-07 21:31:03 +08:00
@mlkr 好像这样子之后, 会有数据没有被加到队列里去?
Licsber
2018-12-07 21:32:33 +08:00
@littlewing channel 是啥.. 刚学 go 还没看到 我去看看 谢谢
mritd
2018-12-07 21:37:36 +08:00
大哥你这个 ... channel 啊
heimeil
2018-12-07 22:00:55 +08:00
Q = Q[1:]瞬间执行多次,这个并发直接把 Q 给 cut 没了,再访问 Q 的任意下标自然就崩了。

要非得用 slice 就别直接操作 slice,另外封装两个增删方法,加上锁。
mlkr
2018-12-07 22:12:18 +08:00
@Licsber
func queueOut() string {
res := Q[0]
if len(Q) == 1 {
Q[0] = ""
return res
}
// 加写锁
lock.Lock()
Q = Q[1:]
lock.Unlock()
return res
}
AngryPanda
2018-12-07 22:14:49 +08:00
channel 没学等于没学 go
Licsber
2018-12-07 22:20:20 +08:00
@mritd 感谢 成功解决问题
Licsber
2018-12-07 22:21:39 +08:00
@heimeil 对, 看了 channel 之后恍然大悟 谢谢
Licsber
2018-12-07 22:22:27 +08:00
@AngryPanda 刚看到 go routine , 感谢
现在接触的 go 给我一种我一直在处理错误(err)的错觉
mritd
2018-12-07 22:22:29 +08:00
@Licsber #12 channel 是 go 的神器啊,怎么能不用呢
Licsber
2018-12-07 22:23:03 +08:00
@mlkr 感谢
Licsber
2018-12-07 22:24:09 +08:00
@mritd 刚刚看到 go 的 channel , 感谢!

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/515496

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX