어떻게 Go에서 라인 안전 대기열을 구축합니까?
18618 단어 queuegomultithreadingsynchronization
왜 라인 안전 대기열이 필요합니까?
고려this 대기열이 있는 go 프로그램.이것은 이 프로그램의
main
기능이다.이것은 크기가 1인 대기열을 만들고 요소를 삽입합니다.그리고 삽입된 요소를 삭제하기 위해 두 개의 go 루틴을 생성합니다.func main() {
q := CreateQueue(1)
q.Insert(0)
// Start two go routines
for i := 0; i < 2; i++ {
// This go routine will try to call the Remove function of the queue.
go func(q *Queue) {
if removed, e := q.Remove(); e == nil {
fmt.Printf("Removed %d\n", removed)
} else {
fmt.Printf("got error %v\n", e)
}
}(q)
}
time.Sleep(100 * time.Second)
}
Remove
기능도 참조하십시오.나는 time.Sleep
함수를 추가하여 일이 너무 오래 걸리면 무슨 일이 일어날지 나타냈다(1ns=1x10-9s는 우리에게 그리 길지 않을 수도 있지만 컴퓨터에게는 그렇게 말할 수 없다)😉) 다중 스레드 환경에서func (q *Queue) Remove() (int, error) {
if len(q.q) > 0 {
time.Sleep(1 * time.Nanosecond)
item := q.q[0]
q.q = q.q[1:]
return item, nil
}
return 0, errors.New("Queue is empty")
}
단일 스레드 환경에서 아무런 문제가 발생하지 않습니다. 모든 것이 예상대로 작동합니다. 즉, 두 개의 Remove
호출은 첫 번째 호출 요소를 삭제하고 두 번째 호출에 오류를 되돌려줍니다.그러나 다선정 환경에 대해서는 아주 재미있는 일이 일어난다.만약 운동장에서 이 프로그램을 실행한다면, 바둑에서 panic 점을 받을 것이다.예를 들면 다음과 같습니다.
panic: runtime error: index out of range [0] with length 0
goroutine 6 [running]:
main.(*Queue).Remove(0xc00000c0a0, 0x0, 0x0, 0x0)
/tmp/sandbox828372841/prog.go:33 +0xf9
main.main.func1(0xc00000c0a0)
/tmp/sandbox828372841/prog.go:56 +0x32
created by main.main
/tmp/sandbox828372841/prog.go:55 +0xcd
Program exited: status 2.
그럼 무슨 일이 일어났어요?
이 두 go 루틴은 하나하나 병행하여 실행되기 시작한다.first go 프로세스 호출
Remove
을 한 다음 Remove
(즉 1)의 길이를 검사한 다음 if
블록에 들어가서 1ns를 휴면합니다.현재 두 번째 go 프로세스도 Remove
를 호출하여 길이를 검사하고 1을 길이로 받은 후 if
블록에 들어가 1ns를 잔다.얘는 왜 들어갔어?퍼스트 고 프로세스가 휴면 상태이기 때문에 대기열에서 요소를 삭제하지 않았기 때문에 길이는 틀림없이 1입니다.현재 두 번째 고례가 수면 상태에 있을 때 첫 번째 고례는 깨어나 원소를 제거한다.두 번째 원소가 깨어나면 대기열의 첫 번째 원소를 가져오려고 시도하지만 존재하지 않습니다!이것은 우리의 공황, 즉 runtime error: index out of range [0] with length 0
을 설명한다.우리는 상술한 실험에서 무엇을 배웠습니까?
time.Sleep(1 * time.Nanosecond)
의 형식으로 나타난 작은 지연, 즉 수면 1ns는 문제를 일으키기에 충분하다.이것은 루틴이 우리가 작성한 코드에 대해 얼마나 민감한지 설명한다🤯, 즉, 만약 내가 이 함수에 운행 시간 > = 1ns의 줄을 추가한다면, 우리는 이 문제를 보게 될 것이다.지금 나는 우리가 왜 다선정 환경에서 서로 다른 방식으로 조작을 처리해야 하는지 이미 잘 알고 있다고 생각한다.그러면 해결 방안은 무엇일까요?
TLDR;솔루션은 사용mutex입니다.
직관적으로 말하자면, 우리는 어떤 주어진 실례에서 하나의 라인
Remove
만 대기열에서 퇴출될 수 있도록 하는 메커니즘이 필요하다.특히 if
블록이 먼저 문제를 일으켰다.여기에 if
블록은 Remove
함수critical section라고 불린다.따라서 우리는 루틴이 Remove
함수에 접근할 때 어떤 방식으로 이 함수의 관건적인 부분을 잠그고, 반드시 실행해야 할 조작을 완성할 때 이 함수를 잠금해제할 것이다.우리는'잠금'과'잠금 해제'를 실현하기 위해 바둑에서 하나mutex
를 사용했다.mutex
는 모든 주어진 실례에서 하나의 라인만 임계 구간에 접근할 수 있도록 확보할 것이다.그래서 당신Remove
이 if
블록에 이르기 전에 우리는 Lock
아래의 조작이 필요합니다. 우리가 완성한 후에 우리는 Unlock
할 수 있습니다.This는 업데이트된 코드 세그먼트입니다. 다음은 업데이트된 코드Remove
입니다. 지금은 라인이 안전합니다.func (q *Queue) Remove() (int, error) {
q.mu.Lock()
defer q.mu.Unlock()
if len(q.q) > 0 {
time.Sleep(1 * time.Nanosecond)
item := q.q[0]
q.q = q.q[1:]
return item, nil
}
return 0, errors.New("Queue is empty")
}
나는 대열struct
에 상호 배척체를 포함했는데, 이렇게 대열의 모든 실례는 자신의 상호 배척체를 가지고 있다.만약 네가 지금 이 프로그램을 실행한다면, 너는 모든 것이 예상한 대로 잘 작동하는 것을 볼 수 있을 것이다.더 이상 당황하지 않다✨!
그것은 어떻게 일합니까?
우리가
q.mu.Lock()
자물쇠를 사용할 때, go 루틴 if
블록만 접근할 수 있도록 보장합니다.함수 defer q.mu.Unlock()
는 함수가 되돌아온 후에 잠금을 풀 수 있도록 보장할 뿐입니다.전화하지 말고 Unlock
다음에 무슨 일이 일어날지 생각해 보세요.😉.비밀번호 다 주세요.😡
네, 들었어요.😅. 이것은 다음과 같습니다.
import (
"errors"
"time"
"sync"
)
type Queue struct {
mu sync.Mutex
capacity int
q []int
}
// FifoQueue
type FifoQueue interface {
Insert()
Remove()
}
// Insert inserts the item into the queue
func (q *Queue) Insert(item int) error {
q.mu.Lock()
defer q.mu.Unlock()
if len(q.q) < int(q.capacity) {
q.q = append(q.q, item)
return nil
}
return errors.New("Queue is full")
}
// Remove removes the oldest element from the queue
func (q *Queue) Remove() (int, error) {
q.mu.Lock()
defer q.mu.Unlock()
if len(q.q) > 0 {
item := q.q[0]
q.q = q.q[1:]
return item, nil
}
return 0, errors.New("Queue is empty")
}
// CreateQueue creates an empty queue with desired capacity
func CreateQueue(capacity int) *Queue {
return &Queue{
capacity: capacity,
q: make([]int, 0, capacity),
}
}
즐거운 코딩!오늘 새로운 걸 배웠으면 좋겠어요.😄.
Reference
이 문제에 관하여(어떻게 Go에서 라인 안전 대기열을 구축합니까?), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/hvydya/how-to-build-a-thread-safe-queue-in-go-lbh텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)