Golang에서 한 번에 실행할 고 루틴 풀을 어떻게 정의 하시겠습니까?
TL; TR : 마지막 부분으로 가서이 문제를 어떻게 해결할 수 있는지 알려주세요.
저는 오늘 아침 Python에서 Golang을 사용하기 시작했습니다. 다른 명령 줄 인수를 사용하여 약간 의 동시성을 사용하여 Go에서 닫힌 소스 실행 파일을 여러 번 호출하고 싶습니다 . 내 결과 코드는 잘 작동하지만 개선하기 위해 귀하의 의견을 듣고 싶습니다. 초기 학습 단계이므로 워크 플로도 설명하겠습니다.
단순성을 위해 여기에서이 "외부 폐쇄 소스 프로그램"이 zenity
명령 줄에서 그래픽 메시지 상자를 표시 할 수있는 Linux 명령 줄 도구라고 가정합니다.
Go에서 실행 파일 호출
따라서 Go에서 다음과 같이 이동합니다.
package main
import "os/exec"
func main() {
cmd := exec.Command("zenity", "--info", "--text='Hello World'")
cmd.Run()
}
이것은 제대로 작동합니다. 참고 .Run()
로 기능적 동등 .Start()
하였다 .Wait()
. 이것은 훌륭하지만이 프로그램을 한 번만 실행하고 싶다면 전체 프로그래밍 작업이 그만한 가치가 없을 것입니다. 그러니 그냥 여러 번 해봅시다.
실행 파일을 여러 번 호출
이제이 작업을 수행 했으므로 사용자 지정 명령 줄 인수를 사용하여 내 프로그램을 여러 번 호출하고 싶습니다 (여기서는 i
단순함을 위해).
package main
import (
"os/exec"
"strconv"
)
func main() {
NumEl := 8 // Number of times the external program is called
for i:=0; i<NumEl; i++ {
cmd := exec.Command("zenity", "--info", "--text='Hello from iteration n." + strconv.Itoa(i) + "'")
cmd.Run()
}
}
좋아, 해냈어! 하지만 여전히 Go over Python의 이점을 볼 수 없습니다.이 코드는 실제로 직렬 방식으로 실행됩니다. 다중 코어 CPU를 사용하고 있으며이를 활용하고 싶습니다. 따라서 고 루틴과의 동시성을 추가해 보겠습니다.
고 루틴, 또는 내 프로그램을 병렬로 만드는 방법
a) 첫 번째 시도 : 모든 곳에 "go"를 추가하십시오.
더 쉽게 호출하고 재사용 할 수 있도록 코드를 다시 작성하고 유명한 go
키워드를 추가해 보겠습니다 .
package main
import (
"os/exec"
"strconv"
)
func main() {
NumEl := 8
for i:=0; i<NumEl; i++ {
go callProg(i) // <--- There!
}
}
func callProg(i int) {
cmd := exec.Command("zenity", "--info", "--text='Hello from iteration n." + strconv.Itoa(i) + "'")
cmd.Run()
}
아무것도! 무엇이 문제입니까? 모든 고 루틴이 한 번에 실행됩니다. zenity가 실행되지 않는 이유를 모르겠지만 AFAIK, zenity 외부 프로그램이 초기화되기 전에 Go 프로그램이 종료되었습니다. 이것은 time.Sleep
zenity의 8 개 인스턴스가 자체적으로 시작될 수 있도록 몇 초 동안 기다리는 것으로 확인되었습니다 . 그래도 이것이 버그로 간주 될 수 있는지 모르겠습니다.
설상가상으로 실제로 호출하고 싶은 실제 프로그램이 실행되는 데 시간이 걸립니다. 내가 4 코어 CPU에서 병렬로이 프로그램의 8 개 인스턴스를 실행하면, 그것은 ... 내가 가서 행동을 goroutines 방법 일반 모르지만, 시간이 컨텍스트 스위칭의 많은 일을 낭비 야의 exec.Command
것 (8 개) 다른 스레드에 zenity 8 번 시작 . 설상가상으로이 프로그램을 10 만번 이상 실행하고 싶다. 고 루틴에서이 모든 것을 한 번에 수행하는 것은 전혀 효율적이지 않습니다. 그래도 4 코어 CPU를 활용하고 싶습니다!
b) 두 번째 시도 : 고 루틴 풀 사용
온라인 리소스는 sync.WaitGroup
이러한 종류의 작업에 의 사용을 권장하는 경향 이 있습니다. 이 접근 방식의 문제는 기본적으로 goroutines 일괄 작업하는 것입니다 : 내가 네 멤버의 WaitGroup의 작성하는 경우, 이동 프로그램을 기다리는 모든 4 개 프로그램의 새로운 배치를 호출하기 전에 마무리로 4 개 외부 프로그램. 이것은 효율적이지 않습니다. CPU가 다시 한번 낭비됩니다.
일부 다른 리소스에서는 버퍼링 된 채널을 사용하여 작업을 수행하도록 권장했습니다.
package main
import (
"os/exec"
"strconv"
)
func main() {
NumEl := 8 // Number of times the external program is called
NumCore := 4 // Number of available cores
c := make(chan bool, NumCore - 1)
for i:=0; i<NumEl; i++ {
go callProg(i, c)
c <- true // At the NumCoreth iteration, c is blocking
}
}
func callProg(i int, c chan bool) {
defer func () {<- c}()
cmd := exec.Command("zenity", "--info", "--text='Hello from iteration n." + strconv.Itoa(i) + "'")
cmd.Run()
}
추한 것 같습니다. 채널은 이러한 목적을위한 것이 아닙니다. 저는 부작용을 이용하고 있습니다. 나는의 개념을 좋아 defer
하지만 내가 만든 더미 채널에서 값을 꺼내기 위해 함수 (람다 포함)를 선언해야하는 것을 싫어합니다. 아, 그리고 물론 더미 채널을 사용하는 것은 그 자체로 추악합니다.
c) 세 번째 시도 : 모든 아이들이 죽으면 죽는다.
이제 거의 끝났습니다. 또 다른 부작용을 고려해야합니다. 모든 제 니티 팝업이 닫히기 전에 Go 프로그램이 닫힙니다. 이것은 루프가 끝날 때 (8 번째 반복에서) 프로그램이 끝나는 것을 방해하는 것이 없기 때문입니다. 이번에 sync.WaitGroup
는 유용 할 것입니다.
package main
import (
"os/exec"
"strconv"
"sync"
)
func main() {
NumEl := 8 // Number of times the external program is called
NumCore := 4 // Number of available cores
c := make(chan bool, NumCore - 1)
wg := new(sync.WaitGroup)
wg.Add(NumEl) // Set the number of goroutines to (0 + NumEl)
for i:=0; i<NumEl; i++ {
go callProg(i, c, wg)
c <- true // At the NumCoreth iteration, c is blocking
}
wg.Wait() // Wait for all the children to die
close(c)
}
func callProg(i int, c chan bool, wg *sync.WaitGroup) {
defer func () {
<- c
wg.Done() // Decrease the number of alive goroutines
}()
cmd := exec.Command("zenity", "--info", "--text='Hello from iteration n." + strconv.Itoa(i) + "'")
cmd.Run()
}
끝난.
내 질문
- 한 번에 실행되는 고 루틴 수를 제한하는 다른 적절한 방법을 알고 있습니까?
나는 스레드를 의미하지 않습니다. Go가 내부적으로 고 루틴을 관리하는 방법은 관련이 없습니다. 한 번에 시작되는 고 루틴 수를 제한하는 것을 의미합니다. exec.Command
호출 될 때마다 새 스레드를 생성하므로 호출되는 시간을 제어해야합니다.
- 그 코드가 괜찮아 보입니까?
- 이 경우 더미 채널 사용을 피하는 방법을 알고 있습니까?
나는 그러한 더미 채널이 갈 길이라고 스스로 확신 할 수 없다.
I would spawn 4 worker goroutines that read the tasks from a common channel. Goroutines that are faster than others (because they are scheduled differently or happen to get simple tasks) will receive more task from this channel than others. In addition to that, I would use a sync.WaitGroup to wait for all workers to finish. The remaining part is just the creation of the tasks. You can see an example implementation of that approach here:
package main
import (
"os/exec"
"strconv"
"sync"
)
func main() {
tasks := make(chan *exec.Cmd, 64)
// spawn four worker goroutines
var wg sync.WaitGroup
for i := 0; i < 4; i++ {
wg.Add(1)
go func() {
for cmd := range tasks {
cmd.Run()
}
wg.Done()
}()
}
// generate some tasks
for i := 0; i < 10; i++ {
tasks <- exec.Command("zenity", "--info", "--text='Hello from iteration n."+strconv.Itoa(i)+"'")
}
close(tasks)
// wait for the workers to finish
wg.Wait()
}
There are probably other possible approaches, but I think this is a very clean solution that is easy to understand.
A simple approach to throttling (execute f()
N times but maximum maxConcurrency
concurrently), just a scheme:
package main
import (
"sync"
)
const maxConcurrency = 4 // for example
var throttle = make(chan int, maxConcurrency)
func main() {
const N = 100 // for example
var wg sync.WaitGroup
for i := 0; i < N; i++ {
throttle <- 1 // whatever number
wg.Add(1)
go f(i, &wg, throttle)
}
wg.Wait()
}
func f(i int, wg *sync.WaitGroup, throttle chan int) {
defer wg.Done()
// whatever processing
println(i)
<-throttle
}
I wouldn't probably call the throttle
channel "dummy". IMHO it's an elegant way (it's not my invention of course), how to limit concurrency.
BTW: Please note that you're ignoring the returned error from cmd.Run()
.
try this: https://github.com/korovkin/limiter
limiter := NewConcurrencyLimiter(10)
limiter.Execute(func() {
zenity(...)
})
limiter.Wait()
'Nice programing' 카테고리의 다른 글
모든 필드에 값이있을 때까지 제출 단추 비활성화 (0) | 2020.11.14 |
---|---|
0과 1 사이의 rand () (0) | 2020.11.14 |
ConEmu에서 열기 오른쪽 클릭 메뉴 창 7 (0) | 2020.11.14 |
스크립트의 ggplot 플롯이 Rstudio에 표시되지 않습니다. (0) | 2020.11.14 |
ES6 : 새 키워드없이 클래스 생성자 호출 (0) | 2020.11.14 |