Golang Notları #8 Go Scheduling

Mustafa Akseli
5 min readFeb 2, 2022

Merhabalar;

Go Scheduling, Goroutine leri çalıştırmak üzere, CPU ve yazdığımız uygulama arasındaki işlemleri organize eden yapıdır.

Photo by Anne Nygård on Unsplash

Goroutine i tekrar hatırlayacak olursak; kaynaklar yeterli olduğu müddetçe, aynı anda (concurrent) birden fazla iş yapabilmenin golang üzerindeki yoludur.

Process ve thread nedir ?

İşletim sisteminde, bir işlemin gerekleşmesi için önce “Process” meydana getirilir. Daha sonra bu süreci, iş parçacıklarına bölerek görevi yerine getirmek üzere “thread” ler oluşturulur.

Örnek üzerinden görselleştirme adına; Kahvaltı isimli bir işem&süreç ( process ) çalıştırmamız ve görevleri tamamlamamız gerekiyor diyelim. Bu process in tamamlanması için aşağıdaki işlemlerin (thread lerin) uygulanması gerekmektedir;
1. Çay veya Kahve için sıcak su kaynatılması
2. Yumurta pişirilmesi
3. Tost makinesi ısıtılması
4. Ekmeğin tost makinasına konulması
n…..

Ben buradaki bir işlemin bitmesini beklemeden diğerlerini yapabilirim. Örneğin, çay suyu kaynarken boşta beklemek yerine, zamandan kazanıp yumurta pişirme işini yapabilirim. Dolayısyla “Kahvaltı process” benim için n adet “thread” a bölünmesi anlamına gelmektedir.

İşletim sistemi, her bir işlem ve alt işlemin gerçekleştirilmesi kaynak yönetimi yapması gerekir. Yapılması beklenen işin gereklerine istinaden, Process veya Thread üzerinden görevleri tamamlamak üzerine kod yazarız.

Yeni bir process açmak kaynak kullanımı adına, thread a göre daha büyük maliyet demektir. Eğer özellikle yeni bir process e ihtiyacımız yok ise thread lar ile çalışmamız daha uygun olacaktır. Goroutine bize thread lar üzerinde çalışma olanağı tanır.

Genel bilgi bölümündeki process ve thread a değindikten sonra thread çeşitleri ile devam edelim;

Kernel level thread;
Bu tip bir thread, KERNEL yani işletim sistemi tarafından yönetilmektedir. Yeni bir thread oluşturmak ve aralarında geçiş yapmak için KERNEL ile iletişime geçmemiz gerekli. Bu işlem esnasında, işletim sistemleri çok fazla efor sarfeder. Yönetimi zor ve user level thread a göre daha maliyetlidir.

User level thread;
Çalışma anında ( runtime ), user seviyesinde thread oluşturabilme ve yönetebilme için gerekli yapıdır.

Peki bu birden fazla işlemleri kim nasıl yönetir ?

Scheduler yapısı yönetir. Schedule yapıları, aynı anda çalışması gereken birden fazla işin gerçekleştirilmesinin nasıl olacağını belirler. Golang aksi belirtilmedikçe, Cooperative scheduling uygular.

Cooperative ve Preemptive yaklaşımı görsel üzerinden şu şekilde anlamlandırabiliriz.

Scheduler türleri -> http://a1bert.kapsi.fi/DIP/DIP.fm.html

Preemptive tipi; Bu tipte çalışan bir scheduler, bir işlemi koşul gözetmeden durdurup, çalışma sırasını başka bir işleme verebilir. Bir işlemin standart zaman aralıklarında ( 10 ms ) durdurulup diğer işlerin yürütülmesi sağlanır. İşlem beklemesi için global runnable queue tarafına alınır. Sonra tekrar aynı işe dönüleceği zaman Local runnable queue tarafına getirilir. Golang te 1.2 den sonra GOMAXPROCS=1 tanımlaması ile bu şekilde çalışabilir. Türkçe olarak kesintili zamanlama olarak adlandırma görebilirsiniz.

Cooperative tipi; Aynı zamanda non-preemptive olarak adlandırılır.
CPU ya yapılması için, işi göndeririz fakat durdurulması için zorlamayız. Bu process; CPU ile işi bittiğinde, I/O beklediğinde veya başka bir process’in işini bitirmesinde ihtiyaç duyduğunda CPU’dan gönüllü olarak ayrılır ve diğer process’lerin kullanabilmesi için CPU’yu serbest bırakır.

İşletim sistemi Scheduling hakkında daha detaylı bilgi almak için Ali Sezişli ‘nin yazısına bakabilirsiniz.

Golang üzerinde, concurrent işlemlerin yürütülmesi için bir dizi kontrollerin gerçekleşmesi gerekmetedir. Thread yönetim için Goroutine, scheduler tip olarak Cooperative scheduler olarak çalıştırılır, fakat Preemptive çalışma için de yakşamın uygulanması mümkündür ( #1 #2 ).

GPM model nedir?
G (
Goroutine): Goroutine i temsil eden user level thread dir. Yapılması gereken işlemi, değişkenleri ve kaldığı yerden devam edebilmesi için stack, status ve program program counter gibi değerleri içerir.

M(Machine): İşletim sistemi üzerinde çalışan kernel thread i temsil eder.

P (Processor): Logical processor ü (yani işlemciyi) temsil eder. G ve M arasındaki bağlantının kurulabileceği işlemciyi belirtir.

Bu tanımlamalardan sonra şu özeti geçebiliriz. Scheduling birden fazla Goroutine leri, CPU üzerinde çalışan birden falza M ( kernel thread ) üzerinde nasıl dağıtılması gerektiğini belirler.

Goroutine State;
1. Waiting; Herhangi bir şeyin gerçekleşmesi için bekleme modunu temsil eder. Bir network datasını, kilitleme mekanizması (mutex vb) veya syscall bekliyor olabilir.
2. Runnable; Bir goroutine nin bir M üzerinde çalışmaya hazır olduğunu belirtir.
3. Executing; Bir M üzerinde bir gorotine nin çalıştığını ifade ediyor.

GPM modelin queue sistemini kısaca tarif etmek için aşağıdaki görsele ihtiyacımız var. KISALTMA -> GRQ ( Global Runnable Queue ) ve LRQ ( Local Runnable Queue )

Bir goroutine, herhangi bir P ile ilişkilendirildiğinde Local Runnable Queue da işlem görmeye başlar. Diğer zamanlarda Global Runnable Queue tarafına alınır. Önceki yazımda ( #4 [ Basic ] Goroutine ve Channels ) kod örneği olarak verdiğim Channel süreci aslında burada bir Queue (kuyruk) dur. Bir G üzerinden Channel a değişken gönderir ve alırken, ilgili G ler, bulunduğu Local Runnable Queue dan Channel Queue tarafına alınır ve geri LRQ ya dönmesi sağlanır. Bu geçiş işlemlerini yönetimini Go Schedule yapar.

Tek CPU bir makinede çalışmak durumdaysanız “Starving to Death” hakkında araştırma yapabilirsiniz.

GPM bileşenlerinin işlem yapma sırası, Queue yönetimi, G (goroutine) lerin çokluğuna binaen yeni bir P ( proccess ) açılmasıyla diğer P lerden G alınması ( Work Stealing ) işlemlerinin hepsi Go Schedule ile yapılmaktadır.

Son olarak Golang Schedule çalışması şu aşamaları içerir;
// G çalıştırmak için Scheduling cycle in 61 de biri kadar zamanda kontrol yapar. Yeni G var ise çalıştırır.
// Yeni çalıştırılacak G yok ise kendi Local Runnable Queue kontrolü yapar. G var ise çalıştırır.
// Yok ise diğer P lerden G almaya çalışır ( Stealing work ) böylelikle, tek bir P üzerinde çok fazla iş birikmesinin önüne geçilmiş olur. Thread ler deki işler adil dağılmış olur.
// Yine G bulamazsa Global Runnable Queue kontrolü yapar.

runtime.schedule() {
// only 1/61 of the time, check the global runnable queue for a G.
// if not found, check the local queue.
// if not found,
// try to steal from other Ps.
// if not, check the global runnable queue.
// if not found, poll network.
}

Kod üzerinden örnek için bir sonraki yazıda görüşmek üzere ;)

Kaynaklar ;
* https://developpaper.com/what-is-the-gpm-scheduler-for-go/
* https://www.ardanlabs.com/blog/2018/08/scheduling-in-go-part2.html
* https://medium.com/random-life-journal/scheduling-in-go-727c9b88c93a
* https://taesunny.github.io/programming/go-scheduling/
[TR] https://www.youtube.com/watch?v=Mx-Vu0dK49k

--

--

Mustafa Akseli

#golang #php #backendDEV #golang #linuxcu #docker #bike #baba vee #motosiklet #entrepreneur #bulunsun