Runtime.Gosched tam olarak ne yapıyor?


86

In Tur Go'nun web sitesinin halindeyken 1.5 yayınlamadan önce bir sürümü , kodu parçasıdır var ki böyle görünüyor.

package main

import (
    "fmt"
    "runtime"
)

func say(s string) {
    for i := 0; i < 5; i++ {
        runtime.Gosched()
        fmt.Println(s)
    }
}

func main() {
    go say("world")
    say("hello")
}

Çıktı şuna benzer:

hello
world
hello
world
hello
world
hello
world
hello

Beni rahatsız eden şey, runtime.Gosched()kaldırıldığında programın artık "dünya" yazmamasıdır.

hello
hello
hello
hello
hello

Neden böyle? İnfazı nasıl runtime.Gosched()etkiler?

Yanıtlar:


143

Not:

Go 1.5'ten itibaren, GOMAXPROCS donanımın çekirdek sayısına ayarlanmıştır: golang.org/doc/go1.5#runtime , 1.5'ten önceki orijinal cevabın altında.


GOMAXPROCS ortam değişkenini belirtmeden Go programını çalıştırdığınızda, Go programlarının tek bir işletim sistemi iş parçacığında yürütülmesi planlanır. Bununla birlikte, programın çok iş parçacıklı görünmesini sağlamak için (gorutinler bunun içindir, değil mi?), Go planlayıcısının bazen yürütme bağlamını değiştirmesi gerekir, böylece her bir gorutin kendi işini yapabilir.

Dediğim gibi, GOMAXPROCS değişkeni belirtilmediğinde, Go çalışma zamanının yalnızca bir iş parçacığı kullanmasına izin verilir, bu nedenle, gorutin hesaplamalar veya hatta IO (düz C işlevleriyle eşlenir) gibi bazı geleneksel işleri gerçekleştirirken yürütme bağlamlarını değiştirmek imkansızdır. ). Bağlam, yalnızca Go eşzamanlılık ilkelleri kullanıldığında, örneğin birkaç kanalı açtığınızda veya programlayıcıya bağlamları değiştirmesini açıkça söylediğinizde (bu sizin durumunuzdur) değiştirilebilir - bunun için budur runtime.Gosched.

Kısaca, bir gruptaki yürütme bağlamı çağrıya ulaştığında Gosched, programlayıcıya yürütmeyi başka bir gorutine değiştirme talimatı verilir. Sizin durumunuzda, ana (programın 'ana' iş parçacığını temsil eden) ve ek olarak oluşturduğunuz iki gorutin vardır go say. GoschedÇağrıyı kaldırırsanız , yürütme bağlamı asla birinci gorutinden ikinciye aktarılmaz, dolayısıyla sizin için "dünya" olmaz. Zaman Goschedmevcut olduğu, zamanlayıcı aktarır 'Merhaba' ve 'dünya içiçe böylece tam tersi saniye ve yardımcısı ilk goroutine her döngünün yürütme.

Bilginize, buna 'işbirliğine dayalı çoklu görev' denir: gorutinler kontrolü açıkça diğer gorutinlere vermelidir. Çoğu çağdaş işletim sisteminde kullanılan yaklaşım 'önleyici çoklu görev' olarak adlandırılır: yürütme iş parçacıkları kontrol aktarımı ile ilgilenmez; programlayıcı bunun yerine yürütme bağlamlarını şeffaf bir şekilde değiştirir. İşbirlikçi yaklaşım, 'yeşil iş parçacıkları'nı, yani işletim sistemi iş parçacıklarıyla 1: 1 eşlemeyen mantıksal eşzamanlı eşgüdümleri uygulamak için sıklıkla kullanılır - bu, Go çalışma zamanı ve onun gorutinlerinin uygulanma şeklidir.

Güncelleme

GOMAXPROCS ortam değişkeninden bahsetmiştim ama ne olduğunu açıklamadım. Bunu düzeltmenin zamanı geldi.

Bu değişken pozitif bir sayıya ayarlandığında N, Go çalışma zamanı N, üzerinde tüm yeşil iş parçacıklarının planlanacağı yerel iş parçacıklarını oluşturabilir . Yerel iş parçacığı, işletim sistemi (Windows iş parçacıkları, pthreads vb.) Tarafından oluşturulan bir tür iş parçacığıdır. Bu N, 1'den büyükse, gorutinlerin farklı yerel iş parçacıklarında yürütülmek üzere programlanacağı ve sonuç olarak paralel olarak çalışacağı anlamına gelir (en azından bilgisayarınızın yeteneklerine kadar: sisteminiz çok çekirdekli işlemciye dayanıyorsa, büyük olasılıkla bu iş parçacıkları gerçekten paralel olacaktır; işlemcinizin tek çekirdeği varsa, işletim sistemi iş parçacıklarında uygulanan önleyici çoklu görev paralel yürütme görünürlüğü yaratacaktır).

Ortam değişkenini runtime.GOMAXPROCS()önceden ayarlamak yerine fonksiyon kullanarak GOMAXPROCS değişkenini ayarlamak mümkündür . Programınızda şu anki yerine şuna benzer bir şey kullanın main:

func main() {
    runtime.GOMAXPROCS(2)
    go say("world")
    say("hello")
}

Bu durumda ilginç sonuçlar gözlemleyebilirsiniz. Düzensiz aralıklarla basılmış "merhaba" ve "dünya" satırlarını almanız mümkündür, örn.

hello
hello
world
hello
world
world
...

Bu, gorutinler işletim sistemi iş parçacıklarını ayırmak üzere programlandıysa olabilir. Aslında bu, önleyici çoklu görevin (veya çok çekirdekli sistemlerde paralel işlemenin) nasıl çalıştığıdır: iş parçacıkları paraleldir ve birleşik çıktıları belirsizdir. BTW, Goschedaramayı bırakabilir veya kaldırabilirsiniz , GOMAXPROCS 1'den büyük olduğunda hiçbir etkisi yok gibi görünüyor.

Aşağıda, runtime.GOMAXPROCSçağrı ile programın birkaç çalıştırmasında elde ettiğim şey var .

hyperplex /tmp % go run test.go
hello
hello
hello
world
hello
world
hello
world
hyperplex /tmp % go run test.go
hello
world
hello
world
hello
world
hello
world
hello
world
hyperplex /tmp % go run test.go
hello
hello
hello
hello
hello
hyperplex /tmp % go run test.go
hello
world
hello
world
hello
world
hello
world
hello
world

Bak, bazen çıktı güzel, bazen değil. Eylemdeki belirsizlik :)

Başka bir güncelleme

Görünüşe göre Go derleyicisinin daha yeni sürümlerinde Go çalışma zamanı, gorutinleri yalnızca eşzamanlılık ilkelleri kullanımında değil, aynı zamanda işletim sistemi sistem çağrılarında da üretmeye zorlar. Bu, yürütme bağlamının, IO fonksiyonları çağrılarında da gorutinler arasında değiştirilebileceği anlamına gelir. Sonuç olarak, son Go derleyicilerinde, GOMAXPROCS ayarlanmadığında veya 1'e ayarlandığında bile belirsiz davranış gözlemlemek mümkündür.


İyi iş ! Ancak bu sorunu 1.0.3 sürümünde karşılamadım, garip.
WoooHaaaa

1
Bu doğru. Bunu go 1.0.3 ile kontrol ettim ve evet, bu davranış görünmedi: GOMAXPROCS == 1 ile bile program GOMAXPROCS> = 2. gibi çalıştı. 1.0.3'te zamanlayıcı ayarlandı.
Vladimir Matveev

Sanırım 1.4 derleyicisine göre işler değişti. Bu (-> gobyexample.com/atomic-counters ) işbirliğine dayalı zamanlama oluştururken OPs sorusundaki örnek işletim sistemi iş parçacıkları oluşturuyor gibi görünüyor. Bu doğruysa lütfen cevabı güncelleyin
tez

8
Go 1.5'ten itibaren GOMAXPROCS, donanımın çekirdek sayısına ayarlanmıştır: golang.org/doc/go1.5#runtime
thepanuto

1
@paulkon, Gosched()gerekli olup olmadığı programınıza bağlıdır, GOMAXPROCSdeğere bağlı değildir . Önleyici çoklu görevin kooperatif olana göre etkinliği de programınıza bağlıdır. Programınız G / Ç'ye bağlıysa, eşzamansız G / Ç ile işbirliğine dayalı çoklu görev muhtemelen eşzamanlı iş parçacığı tabanlı G / Ç'ye göre daha verimli olacaktır (yani daha fazla verime sahip olacaktır); Eğer programınız CPU'ya bağlıysa (örneğin, uzun hesaplamalar), işbirliğine dayalı çoklu görev çok daha az yararlı olacaktır.
Vladimir Matveev

8

İşbirlikçi planlama suçludur. Verimsiz, diğer ("dünya" diyelim) gorutin, yasal olarak, ana sonlandırmadan önce / sonra çalıştırma şansı sıfıra sahip olabilir, bu da spesifikasyonlara göre tüm gorutinleri sonlandırır - yani. tüm süreç.


1
tamam, bu yüzden runtime.Gosched()verir. Bu ne anlama geliyor? Kontrolü ana işleve geri mi döndürüyor?
Jason Yeo

5
Bu özel durumda evet. Genellikle programlayıcıdan "hazır" gorutinlerden herhangi birini kasıtlı olarak belirtilmemiş bir seçim sırasına göre başlatmasını ve çalıştırmasını ister.
zzzz
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.