doumi1912 2018-09-05 07:45
浏览 76
已采纳

计算模逆

I want to compute the inverse element of a prime in modular arithmetic. In order to speed things up I start a few goroutines which try to find the element in a certain range. When the first one finds the element, it sends it to the main goroutine and at this point I want to terminate the program. So I call close in the main goroutine, but I don't know if the goroutines will finish their execution (I guess not). So a few questions arise:

1) Is this a bad style, should I have something like a WaitGroup?

2) Is there a more idiomatic way to do this computation?

package main

import "fmt"

const (
    Procs = 8
    P     = 1000099
    Base  = 1<<31 - 1
)

func compute(start, end uint64, finished chan struct{}, output chan uint64) {
    for i := start; i < end; i++ {
        select {
        case <-finished:
            return
        default:
            break
        }
        if i*P%Base == 1 {
            output <- i
        }
    }
}

func main() {
    finished := make(chan struct{})
    output := make(chan uint64)

    for i := uint64(0); i < Procs; i++ {
        start := i * (Base / Procs)
        end := (i + 1) * (Base / Procs)
        go compute(start, end, finished, output)
    }

    fmt.Println(<-output)
    close(finished)
}
  • 写回答

2条回答 默认 最新

  • douniwan_0025 2018-09-05 17:18
    关注

    Is this a bad style, should I have something like a WaitGroup?

    A wait group solves a different problem.

    In general, to be a responsible go citizen here and ensure your code runs and tidies up behind itself, you may need to do a combination of:

    1. Signal to the spawned goroutines to stop their calculations when the result of the computation has been found elsewhere.
    2. Ensure a synchronous process waits for the goroutines to stop before returning. This is not mandatory if they properly respond to the signal in #1, but if you don't wait, there will be no guarantee they have terminated before the parent goroutine continues.

    In your example program, which performs this task and then quits, there is strictly no need to do either. As this comment indicates, your program's main method terminates upon a satisfactory answer being found, at which point the program will end, any goroutines will be summarily terminated, and the operating system will tidy up any consumed resources. Waiting for goroutines to stop is unnecessary.

    However, if you wrapped this code up into a library or it became part of a long running "inverse prime calculation" service, it would be desirable to tidy up the goroutines you spawned to avoid wasting cycles unnecessarily. Additionally, in general, you may have other scenarios in which goroutines store state, hold handles to external resources, or hold handles to internal objects which you risk leaking if not properly tidied away – it is desirable to properly close these.


    Communicating the requirement to stop working

    There are several approaches to communicate this. I don't claim this is an exhaustive list! (Please do suggest other general-purpose methods in the comments or by proposing edits to the post.)

    Using a special channel

    Signal the child goroutines by closing a special "shutdown" channel reserved for the purpose. This exploits the channel axiom:

    A receive from a closed channel returns the zero value immediately

    On receiving from the shutdown channel, the goroutine should immediately arrange to tidy any local state and return from the function. Your earlier question had example code which implemented this; a version of the pattern is:

    func myGoRoutine(shutdownChan <-chan struct{}) {
        select {
        case <-shutdownChan:
            // tidy up behaviour goes here
            return
        // You may choose to listen on other channels here to implement
        // the primary behaviour of the goroutine.
        }
    }
    
    func main() {
        shutdownChan := make(chan struct{})
        go myGoRoutine(shutdownChan)
    
        // some time later
        close(shutdownChan)
    }
    

    In this instance, the shutdown logic is wasted because the main() method will immediately return after the call to close. This will race with the shutdown of the goroutine, but we should assume it will not properly execute its tidy-up behaviour. Point 2 addresses ways to fix this.

    Using a context

    The context package provides the option to create a context which can be cancelled. On cancellation, a channel exposed by the context's Done() method will be closed, which signals time to return from the goroutine.

    This approach is approximately the same as the previous method, with the exception of neater encapsulation and the availability of a context to pass to downstream calls in your goroutine to cancel nested calls where desired. Example:

    func myGoRoutine(ctx context.Context) {
        select {
        case <-ctx.Done():
            // tidy up behaviour goes here
            return
        // Put real behaviour for the goroutine here.
        }
    }
    
    func main() {
        // Get a context (or use an existing one if you are provided with one
        // outside a `main` method:
        ctx := context.Background()
    
        // Create a derived context with a cancellation method
        ctx, cancel := context.WithCancel(ctx)
    
        go myGoRoutine(ctx)
    
        // Later, when ready to quit
        cancel()
    }
    

    This has the same bug as the other case in that the main method will not wait for the child goroutines to quit before returning.

    Waiting (or "join"ing) for child goroutines to stop

    The code which closes the shutdown channel or closes the context in the above examples will not wait for child goroutines to stop working before continuing. This may be acceptable in some instances, while in others you may require the guarantee that goroutines have stopped before continuing.

    sync.WaitGroup can be used to implement this requirement. The documentation is comprehensive. A wait group is a counter which should be incremented using its Add method on starting a goroutine and decremented using its Done method when a goroutine completes. Code can wait for the counter to return to zero by calling its Wait method, which blocks until the condition is true. All calls to Add must occur before a call to Wait.

    Example code:

    func main() {
        var wg sync.WaitGroup
    
        // Increment the WaitGroup with the number of goroutines we're
        // spawning.
        wg.Add(1)
    
        // It is common to wrap a goroutine in a function which performs
        // the decrement on the WaitGroup once the called function returns
        // to avoid passing references of this control logic to the
        // downstream consumer.
        go func() {
            // TODO: implement a method to communicate shutdown.
            callMyFunction()
            wg.Done()
        }()
    
        // Indicate shutdown, e.g. by closing a channel or cancelling a
        // context.
    
        // Wait for goroutines to stop
        wg.Wait()
    }
    

    Is there a more idiomatic way to do this computation?

    This algorithm is certainly parallelizable through use of goroutines in the manner you have defined. As the work is CPU-bound, the limitation of goroutines to the number of available CPUs makes sense (in the absence of other work on the machine) to benefit from the available compute resource.

    See peterSO's answer for a bug fix.

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论
查看更多回答(1条)

报告相同问题?

悬赏问题

  • ¥15 Arduino红外遥控代码有问题
  • ¥15 数值计算离散正交多项式
  • ¥30 数值计算均差系数编程
  • ¥15 redis-full-check比较 两个集群的数据出错
  • ¥15 Matlab编程问题
  • ¥15 训练的多模态特征融合模型准确度很低怎么办
  • ¥15 kylin启动报错log4j类冲突
  • ¥15 超声波模块测距控制点灯,灯的闪烁很不稳定,经过调试发现测的距离偏大
  • ¥15 import arcpy出现importing _arcgisscripting 找不到相关程序
  • ¥15 onvif+openssl,vs2022编译openssl64