группа ожидания с ограничением одновременности, но сбой теста

#go

#Вперед

Вопрос:

Я использовал sync.WaitGroup с goroutine раньше, но я хочу контролировать параллелизм goroutine,

итак, я пишу свою waitgroup с ограничением параллелизма, например:

 package wglimit

import (
    "sync"
)

// WaitGroupLimit ...
type WaitGroupLimit struct {
    ch chan int
    wg *sync.WaitGroup
}

// New ...
func New(size int) *WaitGroupLimit {
    if size <= 0 {
        size = 1 
    }
    return amp;WaitGroupLimit{
        ch: make(chan int, size),  // buffer chan to limit concurrency
        wg: amp;sync.WaitGroup{},
    }
}

// Add ...
func (wgl *WaitGroupLimit) Add(delta int) {
    for i := 0; i < delta; i   {
        wgl.ch <- 1
        wgl.wg.Add(1)
    }
}

// Done ...
func (wgl *WaitGroupLimit) Done() {
    wgl.wg.Done()
    <-wgl.ch
}

// Wait ...
func (wgl *WaitGroupLimit) Wait() {
    close(wgl.ch)
    wgl.wg.Wait()
}
  

И затем я использую это для управления параллелизмом goroutine, например:

 jobs := ["1", "2", "3", "4"] // some jobs

// wg := sync.WaitGroup{} // have no concurrency limit
wg := wglimit.New(2) // limit 2 goroutine
for _, job := range jobs {
    wg.Add(1)
    go func(job string) {
        // job worker
        defer wg.Done()
    }(job)
}
wg.Wait()
  

И, похоже, это сработало при запуске.

Но тест не удался:

 package wglimit

import (
    "runtime"
    "testing"
    "time"
)

func TestGoLimit(t *testing.T) {
    var limit int = 5
    wglimit := New(limit)
    for i := 0; i < 10000; i   {
        wglimit.Add(1)
        go func() {
            defer wglimit.Done()
            time.Sleep(time.Millisecond)
            if runtime.NumGoroutine() > limit 2 {
                println(runtime.NumGoroutine())  // will print 9 , cocurrent limit fail ?
                t.Errorf("FAIL")
            }
        }()
    }
    wglimit.Wait()
}
  

При тестировании числа процедур больше моего предела, похоже, что предел совместного тока не выполняется.

Что-то не так с моим кодом WaitGroupLimit и почему?

Комментарии:

1. Если вы использовали fmt.Print("spawned") с задержкой в 1 секунду, это лучше продемонстрировало бы, есть ли у вас проблема или нет. Ваша текущая проверка на основе runtime не выглядит надежной (у нее, по крайней мере, есть условие гонки).

2. каково ожидаемое поведение этого механизма? если вы попытаетесь выполнить запись в канал, который заполнен, то это выдаст ошибку. Это то, что вы ищете

Ответ №1:

Что-нибудь не так с моим кодом WaitGroupLimit […]?

Нет.

Проблема в том, runtime.NumGoroutine() что она делает не то, что вы, кажется, думаете. Он учитывает все подпрограммы, то есть не только те, которые вы запускаете, но и те, которые использует сама среда выполнения, например, для одновременной сборки мусора. Таким образом, NumGoroutine превышает ваш лимит.

Ваш код в порядке, ваш тест — нет. Не пытайтесь поумнеть в тестировании и проверить, что на самом деле делает ваш код: он блокируется Add до тех пор, пока не будет доступен ограниченный ресурс. Проверьте это, а не количество подпрограмм, которое является просто (плохим) прокси для желаемого поведения в вашем тесте.