Решение на Concurrent Retry Executor от Данислав Киров

Обратно към всички решения

Към профила на Данислав Киров

Резултати

  • 10 точки от тестове
  • 0 бонус точки
  • 10 точки общо
  • 9 успешни тест(а)
  • 0 неуспешни тест(а)

Код

package main
func execute(task func() string, index, retryLimit int, results chan struct {
index int
result string
}, chConcurrentLimit chan struct{}, chNumberOfTasks chan struct{}) {
result := ""
for i := 0; i < retryLimit && result == ""; i++ {
result = task()
results <- struct {
index int
result string
}{index: index, result: result}
}
<-chConcurrentLimit
chNumberOfTasks <- struct{}{}
}
func ConcurrentRetryExecutor(tasks []func() string, concurrentLimit int, retryLimit int) <-chan struct {
index int
result string
} {
numberOfTasks := len(tasks)
tasksCopy := make([]func() string, numberOfTasks)
copy(tasksCopy, tasks)
results := make(chan struct {
index int
result string
}, numberOfTasks)
go func() {
chNumberOfTasks := make(chan struct{}, numberOfTasks)
defer close(chNumberOfTasks)
chConcurrentLimit := make(chan struct{}, concurrentLimit)
for index, task := range tasksCopy {
chConcurrentLimit <- struct{}{}
go execute(task, index, retryLimit, results, chConcurrentLimit, chNumberOfTasks)
}
close(chConcurrentLimit)
for i := 0; i < numberOfTasks; i++ {
<-chNumberOfTasks
}
close(results)
}()
return results
}

Лог от изпълнението

PASS
ok  	_/tmp/d20161115-21147-1m0adbr	0.003s
PASS
ok  	_/tmp/d20161115-21147-1m0adbr	0.026s
PASS
ok  	_/tmp/d20161115-21147-1m0adbr	0.025s
PASS
ok  	_/tmp/d20161115-21147-1m0adbr	0.003s
PASS
ok  	_/tmp/d20161115-21147-1m0adbr	0.003s
PASS
ok  	_/tmp/d20161115-21147-1m0adbr	0.074s
PASS
ok  	_/tmp/d20161115-21147-1m0adbr	0.144s
PASS
ok  	_/tmp/d20161115-21147-1m0adbr	0.224s
PASS
ok  	_/tmp/d20161115-21147-1m0adbr	0.203s

История (4 версии и 11 коментара)

Данислав обнови решението на 11.11.2016 23:44 (преди над 1 година)

+package main
+
+func execute(task func() string, index, retryLimit int, results chan struct {
+ index int
+ result string
+}, ch chan struct{}) {
+ result := ""
+ for i := 0; i < retryLimit && result == ""; i++ {
+ result = task()
+ results <- struct {
+ index int
+ result string
+ }{index: index, result: result}
+ }
+ <-ch
+
+ if len(ch) == 0 {
+ close(results)
+ }
+}
+
+func ConcurrentRetryExecutor(tasks []func() string, concurrentLimit int, retryLimit int) <-chan struct {
+ index int
+ result string
+} {
+ results := make(chan struct {
+ index int
+ result string
+ }, len(tasks))
+
+ ch := make(chan struct{}, concurrentLimit)
+ defer close(ch)
+
+ for index, task := range tasks {
+ ch <- struct{}{}
+ go execute(task, index, retryLimit, results, ch)
+ }
+
+ return results
+}

Прочети отново условието - направих малко уточнение:

Функцията трябва да връща канала без да изчаква извършването на задачите - за това имаме канала.

Извинявай, изглежда сме забравили да го споменем изрично

Данислав обнови решението на 13.11.2016 13:19 (преди над 1 година)

package main
func execute(task func() string, index, retryLimit int, results chan struct {
index int
result string
-}, ch chan struct{}) {
+}, ch chan struct{}, counter *int, numberOfTasks int) {
result := ""
for i := 0; i < retryLimit && result == ""; i++ {
result = task()
results <- struct {
index int
result string
}{index: index, result: result}
}
<-ch
- if len(ch) == 0 {
+ *counter++
+ if *counter == numberOfTasks {
close(results)
}
}
func ConcurrentRetryExecutor(tasks []func() string, concurrentLimit int, retryLimit int) <-chan struct {
index int
result string
} {
+ numberOfTasks := len(tasks)
+ tasksCopy := make([]func() string, numberOfTasks)
+ copy(tasksCopy, tasks)
+
results := make(chan struct {
index int
result string
- }, len(tasks))
+ }, numberOfTasks)
- ch := make(chan struct{}, concurrentLimit)
- defer close(ch)
+ go func() {
+ ch := make(chan struct{}, concurrentLimit)
+ defer close(ch)
- for index, task := range tasks {
- ch <- struct{}{}
- go execute(task, index, retryLimit, results, ch)
- }
+ counter := 0
+ for index, task := range tasksCopy {
+ ch <- struct{}{}
+ go execute(task, index, retryLimit, results, ch, &counter, numberOfTasks)
+ }
+ }()
return results
}

Данислав обнови решението на 13.11.2016 20:33 (преди над 1 година)

package main
func execute(task func() string, index, retryLimit int, results chan struct {
index int
result string
-}, ch chan struct{}, counter *int, numberOfTasks int) {
+}, chConcurrentLimit chan struct{}, chNumberOfTasks chan struct{}) {
result := ""
for i := 0; i < retryLimit && result == ""; i++ {
result = task()
results <- struct {
index int
result string
}{index: index, result: result}
}
- <-ch
-
- *counter++
- if *counter == numberOfTasks {
- close(results)
- }
+ <-chConcurrentLimit
+ <-chNumberOfTasks
}
func ConcurrentRetryExecutor(tasks []func() string, concurrentLimit int, retryLimit int) <-chan struct {
index int
result string
} {
numberOfTasks := len(tasks)
tasksCopy := make([]func() string, numberOfTasks)
copy(tasksCopy, tasks)
results := make(chan struct {
index int
result string
}, numberOfTasks)
go func() {
- ch := make(chan struct{}, concurrentLimit)
- defer close(ch)
+ chNumberOfTasks := make(chan struct{}, numberOfTasks)
+ for i := 0; i < numberOfTasks; i++ {
+ chNumberOfTasks <- struct{}{}
+ }
+ close(chNumberOfTasks)
- counter := 0
+ chConcurrentLimit := make(chan struct{}, concurrentLimit)
for index, task := range tasksCopy {
- ch <- struct{}{}
- go execute(task, index, retryLimit, results, ch, &counter, numberOfTasks)
+ chConcurrentLimit <- struct{}{}
+ go execute(task, index, retryLimit, results, chConcurrentLimit, chNumberOfTasks)
}
+ close(chConcurrentLimit)
+
+ for len(chNumberOfTasks) > 0 {

този busy loop не е правилно решение - има отново лесен и елегантен начин да разбереш че и последната горутина е завършила използвайки текущото количество канали или по някакви други начини

+ }
+ close(results)
}()
return results
}

Удачно ли е да използвам още един канал (chNumberOfTasks) и да чакам да остане празен или отново съм на грешен път?

Освен това искам да попитам относно: Също така, трябва да е възможно няколко инстанции на ConcurrentRetryExecutor да работят едновременно, без да си пречат една на друга. - понеже е написано изрично, това значи ли, че трябва tasks да се копира (както съм направил) понеже е slice или има нещо друго, за което не съм се сетил?

И относно стила се замислих - има ли някаква конвенция за анонимните функции (например колко дълги да са), понеже мога да я изнеса от ConcurrentRetryExecutor или от друга страна - мога да внеса execute вътре също като анонимна, но мисля, че ще стане грозно?

Значително по на "прав" път си - ако махнеш busy loop-а бих казал че имаш валидно решение.

С това изречение искаме да кажем че ако пуснем два ConcurrentRetryExecutor те не трябва да си пречат взаимно - копирането на task нито помага нито пречи в това отношение - по скоро се има предвид да не се ползват някакъв общ state между отделните извиквания на ConcurrentRetryExecutor - ти не го правиш

За конвенция не съм сигурен - принципно аз не обичам нито дълги методи нито дълбоки. Специално в конкретния случай смятам че не е проблем което и от нещата да решиш - не си чак толкова екстремално в който и да е от случаите в контекста на стандартен go код

Също толкова грешно ли ще е ако вместо да пълня предварително chNumberOfTasks, всяка горутина пише по една структура в execute, а в анонимната функция въртя цикъл от 0 до numberOfTasks, в който чета от chNumberOfTasks? Или трябва въобще без цикъл да стане?

Не ти разбрах предложението - като го предадеш ще мога да коментирам.

Самия цикъл не е проблема - проблема е че този цикъл ще заеме 100% cpu-то защото не изчаква нищо а просто постоянно проверява нещо - busy loop.

Данислав обнови решението на 14.11.2016 00:20 (преди над 1 година)

package main
func execute(task func() string, index, retryLimit int, results chan struct {
index int
result string
}, chConcurrentLimit chan struct{}, chNumberOfTasks chan struct{}) {
result := ""
for i := 0; i < retryLimit && result == ""; i++ {
result = task()
results <- struct {
index int
result string
}{index: index, result: result}
}
<-chConcurrentLimit
- <-chNumberOfTasks
+ chNumberOfTasks <- struct{}{}
}
func ConcurrentRetryExecutor(tasks []func() string, concurrentLimit int, retryLimit int) <-chan struct {
index int
result string
} {
numberOfTasks := len(tasks)
tasksCopy := make([]func() string, numberOfTasks)
copy(tasksCopy, tasks)
results := make(chan struct {
index int
result string
}, numberOfTasks)
go func() {
chNumberOfTasks := make(chan struct{}, numberOfTasks)
- for i := 0; i < numberOfTasks; i++ {
- chNumberOfTasks <- struct{}{}
- }
- close(chNumberOfTasks)
+ defer close(chNumberOfTasks)
chConcurrentLimit := make(chan struct{}, concurrentLimit)
for index, task := range tasksCopy {
chConcurrentLimit <- struct{}{}
go execute(task, index, retryLimit, results, chConcurrentLimit, chNumberOfTasks)
}
close(chConcurrentLimit)
- for len(chNumberOfTasks) > 0 {
+ for i := 0; i < numberOfTasks; i++ {
+ <-chNumberOfTasks
}
close(results)
}()
return results
}