Почему это не создает много потоков, когда многие goroutines заблокированы в записи файла в golang?

Как мы знаем в go, поток может быть создан, когда программа должна выполнить блокирующий вызов, такой как системный вызов или вызов библиотеки C через cgo. Некоторый тестовый код:

   package main

   import (
        "io/ioutil"
        "os"
        "runtime"
        "strconv"
    )

    func main() {
        runtime.GOMAXPROCS(2)
        data, err := ioutil.ReadFile("./55555.log")
        if err != nil {
            println(err)
            return
        }
        for i := 0; i < 200; i++ {
            go func(n int) {
                for {
                    err := ioutil.WriteFile("testxxx"+strconv.Itoa(n), []byte(data), os.ModePerm)
                    if err != nil {
                        println(err)
                        break
                    }
                }
            }(i)
        }
        select {}
    }

Когда я запускал его, он не создавал много потоков.

➜ =99=[root /root]$ cat /proc/9616/status | grep -i thread
Threads:    5

Есть идеи?

3 ответа

Я немного изменил вашу программу, чтобы вывести гораздо больший блок

package main

import (
    "io/ioutil"
    "os"
    "runtime"
    "strconv"
)

func main() {
    runtime.GOMAXPROCS(2)
    data := make([]byte, 128*1024*1024)
    for i := 0; i < 200; i++ {
        go func(n int) {
            for {
                err := ioutil.WriteFile("testxxx"+strconv.Itoa(n), []byte(data), os.ModePerm)
                if err != nil {
                    println(err)
                    break
                }
            }
        }(i)
    }
    select {}
}

Это тогда показывает>200 потоков, как вы ожидали

$ cat /proc/17033/status | grep -i thread
Threads:    203

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

Goroutine - это легкий поток, он не эквивалентен потоку операционной системы. Спецификация языка определяет его как "независимый параллельный поток управления в одном и том же адресном пространстве".

Цитирование из документации пакета runtime:

Переменная GOMAXPROCS ограничивает количество потоков операционной системы, которые могут одновременно выполнять код Go на уровне пользователя. Нет ограничений на количество потоков, которые могут быть заблокированы в системных вызовах от имени кода Go; те не считаются с лимитом GOMAXPROCS.

То, что вы запускаете 200 процедур, не означает, что для них будет запущено 200 потоков. Ты устанавливаешь GOMAXPROCS 2, что означает, что одновременно могут выполняться 2 "активные" программы. Новые потоки могут быть порождены, если программа заблокирована (например, ожидание ввода / вывода). Вы не упомянули, насколько велик ваш тестовый файл. Запущенные вами подпрограммы могут закончить его написание слишком быстро.

Статья в блоге Effective Go определяет их как:

Их называют goroutines, потому что существующие термины - потоки, сопрограммы, процессы и т. Д. - дают неточные коннотации. Goroutine имеет простую модель: это функция, выполняемая одновременно с другими goroutines в том же адресном пространстве. Он легкий и стоит немного больше, чем выделение стекового пространства. И стеки начинаются с малого, поэтому они дешевы и растут, выделяя (и освобождая) хранилище кучи по мере необходимости.

Городуны мультиплексируются в несколько потоков ОС, поэтому, если один из них блокируется, например, во время ожидания ввода-вывода, другие продолжают работать. Их дизайн скрывает многие сложности создания потоков и управления ими.

В выпуске 4056 обсуждается, как ограничить количество создаваемых фактических потоков (не goroutine).

В Go 1.2 введено управление потоками в коммите 665feee.

Вы можете увидеть тест, чтобы проверить, действительно ли достигнуто количество созданных потоков в pkg/runtime/crash_test.go#L128-L134:

func TestThreadExhaustion(t *testing.T) {
    output := executeTest(t, threadExhaustionSource, nil)
    want := "runtime: program exceeds 10-thread limit\nfatal error: thread exhaustion"
    if !strings.HasPrefix(output, want) {
        t.Fatalf("output does not start with %q:\n%s", want, output)
    }
}

В этом же файле есть пример для создания фактического потока (для данной программы), используя runtime.LockOSThread():

func testInNewThread(name string) {
    c := make(chan bool)
    go func() {
        runtime.LockOSThread()
        test(name)
        c <- true
    }()
    <-c
}
Другие вопросы по тегам