어떻게 Go에서 라인 안전 대기열을 구축합니까?

스레드 보안 대기열은 다중 스레드 환경에서 사용할 수 있는 대기열로 데이터를 잃어버리지 않습니다.하나의 라인 안전 대기열을 구축하기 전에, 왜 라인 안전 대기열이 필요한지 토론합시다.만약 선결 조건을 알고 있다면 설명을 건너뛰고 본 블로그의 밑부분으로 가서 대기열의 완전한 실현을 찾으십시오😄.

왜 라인 안전 대기열이 필요합니까?
고려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는 모든 주어진 실례에서 하나의 라인만 임계 구간에 접근할 수 있도록 확보할 것이다.그래서 당신Removeif 블록에 이르기 전에 우리는 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),
    }
}
즐거운 코딩!오늘 새로운 걸 배웠으면 좋겠어요.😄.

좋은 웹페이지 즐겨찾기