Как закрыть горутины после закрытия канала

Я пытаюсь написать программу, которая одновременно добывает блок биткойнов. Я настроил его так, чтобы у каждой горутины был начальный начальный одноразовый номер, каждый из которых является долей 4, т.е. 2**64 - 1 (максимальное количество типов uint64) / 1 или 2, или 3, или 4.

Только один из этих майнеров столкнется с правильным одноразовым номером, и когда это произойдет, я хотел бы, чтобы он передал его менеджеру майнера через канал, и когда это произойдет, я бы хотел, чтобы остальные 3 майнера остановили то, что они делают.

Единственная проблема в том, что я понятия не имею, как уничтожить запущенную горутину, и есть ли вообще способ сделать то, о чем я прошу.

func miner(blockNumber int, transactions string, previousHash string, zeroPrefix string, startNonce uint64, nonceChan chan uint64, hashChan chan string) {
    var text string
    var newHash string

    for {
        text = strconv.Itoa(blockNumber) + transactions + previousHash + strconv.FormatUint(startNonce, 10)
        newHash = encrypt(text)

        if startswith(newHash, zeroPrefix) {
            nonceChan <- startNonce
            hashChan  <- newHash

            close(nonceChan)
            close(hashChan)
            break
        } else {
            startNonce++
        }
    }
}

func mine(blockNumber int, transactions string, previousHash string, zeroPrefix int) Block {
    var prefixString string
    var newHash string
    var nonce uint64
    var startNonce uint64

    nonceChan := make(chan uint64)
    hashChan := make(chan string)

    for i := 0; i < zeroPrefix; i++ {
        prefixString += "0"
    }

    start := time.Now()

    for i := 0; i < 4; i++{
        // This line is for deciding at what nonce value a miner should start at.
        startNonce = uint64((float64(i) / 4) * math.Pow(2, 64))

        go func() {
            fmt.Println("Started miner with start nonce of", startNonce)
            miner(blockNumber, transactions, previousHash, prefixString, startNonce, nonceChan, hashChan)
        }()
    }

    nonce = <- nonceChan
    newHash = <- hashChan

    // Here is where I would like to destroy the other three miners

    block := Block{
        blockNumber,
        transactions,
        previousHash,
        newHash,
        nonce,
        zeroPrefix,
        time.Since(start),
    }

    return block
}

person Alexander Hunter    schedule 19.01.2021    source источник


Ответы (2)


вы можете использовать context, который является одним из типичных объектов для обработки завершения процедуры go.

context : Контекст пакета определяет тип контекста, который содержит крайние сроки, сигналы отмены и другие значения в области запроса через границы API и между процессами.

ctx, cancel := context.WithCancel(context.Background()) с этим вы можете создать context с cancel функц.

Просто передайте ctx and cancel в своей процедуре go, и когда вы закончите внутри любой из ваших процедур go, просто сделайте вызов cancel() func(). и ctx.done() будет тогда истинным, а затем первый случай переключения будет истинным, и он вернется из всех ваших подпрограмм go.


func miner( ctx context.Context,  blockNumber int, transactions string, previousHash string, zeroPrefix string, startNonce uint64, nonceChan chan uint64, hashChan chan string) {
    var text string
    var newHash string

    for {
        select {
        case <-ctx.Done():  // if cancel() execute
           return
        default:
            text = strconv.Itoa(blockNumber) + transactions + previousHash + strconv.FormatUint(startNonce, 10)
            newHash = encrypt(text)

            if startswith(newHash, zeroPrefix) {
                nonceChan <- startNonce
                hashChan  <- newHash

                close(nonceChan)
                close(hashChan)
                break
            } else {
                startNonce++
            }

        }

    }
}

func mine(blockNumber int, transactions string, previousHash string, zeroPrefix int) Block {
    var prefixString string
    var newHash string
    var nonce uint64
    var startNonce uint64

    nonceChan := make(chan uint64)
    hashChan := make(chan string)

    for i := 0; i < zeroPrefix; i++ {
        prefixString += "0"
    }

    start := time.Now()
    ctx, cancel := context.WithCancel(context.Background())

    for i := 0; i < 4; i++{
        // This line is for deciding at what nonce value a miner should start at.
        startNonce = uint64((float64(i) / 4) * math.Pow(2, 64))

        go func(ctx context.Context) {
            fmt.Println("Started miner with start nonce of", startNonce)
            miner(ctx, blockNumber, transactions, previousHash, prefixString, startNonce, nonceChan, hashChan)
        }(ctx)
    }

    nonce = <- nonceChan
    newHash = <- hashChan
    cancel()

    // Here is where I would like to destroy the other three miners

    block := Block{
        blockNumber,
        transactions,
        previousHash,
        newHash,
        nonce,
        zeroPrefix,
        time.Since(start),
    }

    return block
}
person Emon46    schedule 19.01.2021
comment
Укажите контекст в качестве первого параметра: Контекст должен быть первым параметром, обычно называемым ctx (golang.org/pkg /контекст) - person TehSphinX; 19.01.2021
comment
@TehSphinX, я только что обновил свой ответ. спасибо за ваш ценный комментарий. - person Emon46; 19.01.2021
comment
Один голос за использование правильной языковой конструкции. Я бы также не отказался от функции отмены. Его можно отменить извне после получения результата. - person TehSphinX; 19.01.2021
comment
полностью согласен с тобой - person Emon46; 19.01.2021
comment
HR Emon Большое спасибо, это именно то, что я искал. - person Alexander Hunter; 20.01.2021

Создайте ctx, cancel := context.WithCancel(context.Background()) в функции, которая запускает все горутины, и передайте ее всем горутинам (как первый параметр в функции).

Когда работу нужно отменить, вызовите функцию cancel. Вы можете сделать это, например. в функции main после получения результата.

В каждой горутине проверьте выбор (в вашем цикле for) для ctx.Done:

select {
case <-ctx.Done():
    return
default:
}

// continue mining

Пример:

func miner(ctx context.Context, ...) {
    defer func() {
        // any necessary cleanup
    }

    for {
        select {
        case <-ctx.Done():
            // abort was called for: exit
            return
        default:
        }

        // continue mining
    }
}
func mine() {
    // use a single channel to get the result. You could 
    // block yourself if you use multiple channels
    chResult := make(chan result)

    // create context
    ctx, cancel := context.WithCancel(context.Background())

    for i := 0; i < 4; i++{
        // ...

        // pass the context into the miner
        go miner(ctx, chResult, ...)
    }

    // block for first miner to be successful
    res := <-chResult
   
    // cancel the other routines
    cancel()
    
    // ...
}

result может быть:

struct result {
    hash  string
    nonce uint64
}
person TehSphinX    schedule 19.01.2021