Làm thế nào để kiểm tra một kênh đã đóng hay chưa mà không đọc nó?


82

Đây là một ví dụ điển hình về chế độ worker & controller trong Go được viết bởi @Jimt, để trả lời cho " Có cách nào thú vị để tạm dừng và tiếp tục bất kỳ quy trình goroutine nào khác trong golang không? "

package main

import (
    "fmt"
    "runtime"
    "sync"
    "time"
)

// Possible worker states.
const (
    Stopped = 0
    Paused  = 1
    Running = 2
)

// Maximum number of workers.
const WorkerCount = 1000

func main() {
    // Launch workers.
    var wg sync.WaitGroup
    wg.Add(WorkerCount + 1)

    workers := make([]chan int, WorkerCount)
    for i := range workers {
        workers[i] = make(chan int)

        go func(i int) {
            worker(i, workers[i])
            wg.Done()
        }(i)
    }

    // Launch controller routine.
    go func() {
        controller(workers)
        wg.Done()
    }()

    // Wait for all goroutines to finish.
    wg.Wait()
}

func worker(id int, ws <-chan int) {
    state := Paused // Begin in the paused state.

    for {
        select {
        case state = <-ws:
            switch state {
            case Stopped:
                fmt.Printf("Worker %d: Stopped\n", id)
                return
            case Running:
                fmt.Printf("Worker %d: Running\n", id)
            case Paused:
                fmt.Printf("Worker %d: Paused\n", id)
            }

        default:
            // We use runtime.Gosched() to prevent a deadlock in this case.
            // It will not be needed of work is performed here which yields
            // to the scheduler.
            runtime.Gosched()

            if state == Paused {
                break
            }

            // Do actual work here.
        }
    }
}

// controller handles the current state of all workers. They can be
// instructed to be either running, paused or stopped entirely.
func controller(workers []chan int) {
    // Start workers
    for i := range workers {
        workers[i] <- Running
    }

    // Pause workers.
    <-time.After(1e9)
    for i := range workers {
        workers[i] <- Paused
    }

    // Unpause workers.
    <-time.After(1e9)
    for i := range workers {
        workers[i] <- Running
    }

    // Shutdown workers.
    <-time.After(1e9)
    for i := range workers {
        close(workers[i])
    }
}

Nhưng mã này cũng có một vấn đề: Nếu bạn muốn xóa một kênh công nhân workerskhi worker()thoát ra, thì sẽ xảy ra khóa chết.

Nếu bạn close(workers[i]), lần sau bộ điều khiển ghi vào nó sẽ gây ra một sự hoảng sợ vì go không thể ghi vào một kênh đã đóng. Nếu bạn sử dụng một số mutex để bảo vệ nó, thì nó sẽ bị kẹt workers[i] <- Runningworkerkhông đọc bất cứ thứ gì từ kênh và ghi sẽ bị chặn và mutex sẽ gây ra khóa chết. Bạn cũng có thể cung cấp một bộ đệm lớn hơn cho kênh như một công việc xung quanh, nhưng nó không đủ tốt.

Vì vậy, tôi nghĩ cách tốt nhất để giải quyết vấn đề này là worker()đóng kênh khi thoát ra, nếu bộ điều khiển tìm thấy một kênh bị đóng, nó sẽ nhảy qua nó và không làm gì cả. Nhưng tôi không thể tìm thấy cách kiểm tra kênh đã bị đóng hay chưa trong trường hợp này. Nếu tôi cố đọc kênh trong bộ điều khiển, bộ điều khiển có thể bị chặn. Vì vậy, hiện tại tôi rất bối rối.

Tái bút: Tôi đã cố gắng phục hồi cơn hoảng sợ đã tăng lên, nhưng nó sẽ đóng lại quy trình gây hoảng sợ. Trong trường hợp này nó sẽ là bộ điều khiển vì vậy nó không được sử dụng.

Tuy nhiên, tôi nghĩ rằng việc triển khai chức năng này trong phiên bản tiếp theo của cờ vây là rất hữu ích đối với đội cờ vây.


Xử lý trạng thái của nhân viên của bạn! Nếu bạn đóng kênh, không cần phải viết lại kênh đó.
jurka

Tại đây, tôi đã thực hiện điều này: github.com/atedja/go-tunnel .
atedja

Câu trả lời:


64

Theo một cách khó hiểu, nó có thể được thực hiện cho các kênh mà người ta cố gắng ghi vào bằng cách khôi phục sự hoảng sợ đã tăng lên. Nhưng bạn không thể kiểm tra xem kênh đã đọc có bị đóng hay không mà không đọc từ kênh đó.

Hoặc bạn sẽ

  • cuối cùng đọc giá trị "true" từ nó ( v <- c)
  • đọc giá trị "đúng" và chỉ báo "không đóng" ( v, ok <- c)
  • đọc giá trị 0 và chỉ báo 'đóng' ( v, ok <- c)
  • sẽ chặn trong kênh đọc mãi mãi ( v <- c)

Về mặt kỹ thuật, chỉ có trang cuối cùng không được đọc từ kênh, nhưng điều đó ít được sử dụng.


1
Tôi đã cố gắng phục hồi cơn hoảng sợ đã tăng lên, nhưng nó sẽ đóng lại quy trình gây hoảng sợ. Trong trường hợp này, nó sẽ controllerkhông được sử dụng :)
Reck Hou

bạn cũng có thể viết hack bằng cách sử dụng gói phản ánh và không an toàn, hãy xem câu trả lời của tôi
youssif

77

Không có cách nào để viết một ứng dụng an toàn mà bạn cần biết liệu một kênh có đang mở mà không cần tương tác với nó hay không.

Cách tốt nhất để thực hiện những gì bạn muốn là sử dụng hai kênh - một cho công việc và một cho biết mong muốn thay đổi trạng thái (cũng như việc hoàn thành thay đổi trạng thái đó nếu điều đó quan trọng).

Các kênh có giá rẻ. Ngữ nghĩa quá tải thiết kế phức tạp không phải là.

[cũng thế]

<-time.After(1e9)

là một cách viết thực sự khó hiểu và không rõ ràng

time.Sleep(time.Second)

Giữ mọi thứ đơn giản và mọi người (bao gồm cả bạn) có thể hiểu chúng.


7

Tôi biết câu trả lời này là quá muộn, tôi đã viết giải pháp này, Thời gian chạy Hacking Go , Nó không an toàn, Nó có thể bị lỗi:

import (
    "unsafe"
    "reflect"
)


func isChanClosed(ch interface{}) bool {
    if reflect.TypeOf(ch).Kind() != reflect.Chan {
        panic("only channels!")
    }
    
    // get interface value pointer, from cgo_export 
    // typedef struct { void *t; void *v; } GoInterface;
    // then get channel real pointer
    cptr := *(*uintptr)(unsafe.Pointer(
        unsafe.Pointer(uintptr(unsafe.Pointer(&ch)) + unsafe.Sizeof(uint(0))),
    ))
    
    // this function will return true if chan.closed > 0
    // see hchan on https://github.com/golang/go/blob/master/src/runtime/chan.go 
    // type hchan struct {
    // qcount   uint           // total data in the queue
    // dataqsiz uint           // size of the circular queue
    // buf      unsafe.Pointer // points to an array of dataqsiz elements
    // elemsize uint16
    // closed   uint32
    // **
    
    cptr += unsafe.Sizeof(uint(0))*2
    cptr += unsafe.Sizeof(unsafe.Pointer(uintptr(0)))
    cptr += unsafe.Sizeof(uint16(0))
    return *(*uint32)(unsafe.Pointer(cptr)) > 0
}

1
go vettrả về "có thể lạm dụng không an toàn.Pointer" ở dòng cuối cùng return *(*uint32)(unsafe.Pointer(cptr)) > 0cptr += unsafe.Sizeof(unsafe.Pointer(uintptr(0))) có tùy chọn nào để làm điều đó mà không mất an toàn.Pointer trong những dòng đó không?
Effi Bar-She'an

2
Bạn cần thực hiện tất cả các phép tính con trỏ trong một biểu thức để giúp bác sĩ thú y vui vẻ. Giải pháp này là một cuộc chạy đua dữ liệu và Go không hợp lệ, bạn cũng phải thực hiện tối thiểu việc đọc đóng với nguyên tử.LoadUint32. Tuy nhiên, đây là một bản hack khá mong manh, nếu hchan thay đổi giữa các phiên bản cờ vây, điều này sẽ bị phá vỡ.
Eloff

1
điều này có lẽ rất thông minh, nhưng nó cảm thấy giống như thêm một vấn đề trên một vấn đề khác
Ярослав Рахматуллин

2

Vâng, bạn có thể sử dụng defaultchi nhánh để phát hiện nó, cho một kênh khép kín sẽ được lựa chọn, ví dụ: đoạn mã sau sẽ lựa chọn default, channel, channel, là người đầu tiên chọn không bị chặn.

func main() {
    ch := make(chan int)

    go func() {
        select {
        case <-ch:
            log.Printf("1.channel")
        default:
            log.Printf("1.default")
        }
        select {
        case <-ch:
            log.Printf("2.channel")
        }
        close(ch)
        select {
        case <-ch:
            log.Printf("3.channel")
        default:
            log.Printf("3.default")
        }
    }()
    time.Sleep(time.Second)
    ch <- 1
    time.Sleep(time.Second)
}

Bản in

2018/05/24 08:00:00 1.default
2018/05/24 08:00:01 2.channel
2018/05/24 08:00:01 3.channel

3
Có một vấn đề với giải pháp này (cũng như go101.org/article/channel-closing.html được viết khá độc đáo đề xuất một giải pháp tương tự) - nó không hoạt động nếu bạn đang sử dụng Kênh đệm và nó chứa dữ liệu
Angad

@Angad Đúng là đây không phải là giải pháp hoàn hảo để phát hiện kênh bị đóng. Đây là một giải pháp hoàn hảo để phát hiện xem việc đọc kênh có bị chặn hay không. (tức là Nếu đọc kênh sẽ chặn, thì chúng ta biết nó không bị đóng; Nếu đọc kênh sẽ không chặn, chúng ta biết nó có thể bị đóng).
Tombrown 52

0

Ngoài việc đóng kênh, bạn có thể đặt kênh của mình thành nil. Bằng cách đó, bạn có thể kiểm tra xem nó có phải là số không.

ví dụ trong sân chơi: https://play.golang.org/p/v0f3d4DisCz

chỉnh sửa: Đây thực sự là một giải pháp không tốt như được minh họa trong ví dụ tiếp theo, bởi vì đặt kênh thành nil trong một hàm sẽ phá vỡ nó: https://play.golang.org/p/YVE2-LV9TOp


chuyển kênh theo địa chỉ (hoặc trong một cấu trúc được chuyển theo địa chỉ)
ChuckCottrill

-1

Từ tài liệu:

Một kênh có thể bị đóng khi đóng chức năng tích hợp. Biểu mẫu gán nhiều giá trị của toán tử nhận báo cáo liệu giá trị nhận được có được gửi trước khi kênh bị đóng hay không.

https://golang.org/ref/spec#Receive_operator

Ví dụ của Golang in Action cho thấy trường hợp này:

// This sample program demonstrates how to use an unbuffered
// channel to simulate a game of tennis between two goroutines.
package main

import (
    "fmt"
    "math/rand"
    "sync"
    "time"
)

// wg is used to wait for the program to finish.
var wg sync.WaitGroup

func init() {
    rand.Seed(time.Now().UnixNano())
}

// main is the entry point for all Go programs.
func main() {
    // Create an unbuffered channel.
    court := make(chan int)
    // Add a count of two, one for each goroutine.
    wg.Add(2)
    // Launch two players.
    go player("Nadal", court)
    go player("Djokovic", court)
    // Start the set.
    court <- 1
    // Wait for the game to finish.
    wg.Wait()
}

// player simulates a person playing the game of tennis.
func player(name string, court chan int) {
    // Schedule the call to Done to tell main we are done.
    defer wg.Done()
    for {
        // Wait for the ball to be hit back to us.
        ball, ok := <-court
        fmt.Printf("ok %t\n", ok)
        if !ok {
            // If the channel was closed we won.
            fmt.Printf("Player %s Won\n", name)
            return
        }
        // Pick a random number and see if we miss the ball.
        n := rand.Intn(100)
        if n%13 == 0 {
            fmt.Printf("Player %s Missed\n", name)
            // Close the channel to signal we lost.
            close(court)
            return
        }

        // Display and then increment the hit count by one.
        fmt.Printf("Player %s Hit %d\n", name, ball)
        ball++
        // Hit the ball back to the opposing player.
        court <- ball
    }
}

2
Câu hỏi đặt ra là làm thế nào để kiểm tra trạng thái đóng mà không cần đọc kênh, tức là trước khi ghi vào nó.
Peter

-5

trước tiên, việc kiểm tra xem kênh có các yếu tố dễ dàng hơn hay không, điều đó sẽ đảm bảo kênh vẫn tồn tại.

func isChanClosed(ch chan interface{}) bool {
    if len(ch) == 0 {
        select {
        case _, ok := <-ch:
            return !ok
        }
    }
    return false 
}

3
Như Dustin đã đề cập , không có cách nào để làm điều này một cách an toàn. Vào thời điểm bạn đi vào ifcơ thể của bạn len(ch)có thể là bất cứ điều gì. (ví dụ: một quy trình trên lõi khác gửi một giá trị đến kênh trước khi lựa chọn của bạn cố gắng đọc).
Dave C

-7

Nếu bạn nghe kênh này, bạn luôn có thể biết kênh đó đã bị đóng.

case state, opened := <-ws:
    if !opened {
         // channel was closed 
         // return or made some final work
    }
    switch state {
        case Stopped:

Nhưng hãy nhớ rằng bạn không thể đóng một kênh hai lần. Điều này sẽ làm tăng sự hoảng sợ.


5
Tôi nói "mà không đọc nó", -1 vì không đọc kỹ câu hỏi.
Reck Hou

> Tái bút: Tôi đã cố gắng phục hồi cơn hoảng loạn đã tăng lên, nhưng nó sẽ đóng lại quy trình gây hoảng sợ. Trong trường hợp này nó sẽ là bộ điều khiển vì vậy nó không được sử dụng. Bạn luôn có thể truy cập func (chan z) {defer func () {// xử lý phục hồi} close (z)}
jurka

Nhưng tôi phải đặt trước bộ điều khiển, và close(z)sẽ được gọi bởi nhân viên thay vì bộ điều khiển.
Reck Hou
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.