9 Coroutine (EşYordam)

9 Coroutine(EşYordam)

Eşyordam(coroutine) iş parçacığına(thread) benzer (multithreading anlamında): Kendi yığınına, kendi yerel değişkenlerine ve kendi program sayacına sahip bir yürütme hattıdır; Ancak global değişkenler ve diğer çoğu şey eşyordamlarca paylaşılır. İş parçacıkları ve eşyordamlar arasındaki temel fark, kavramsal olarak (veya kelimenin tam anlamıyla çok işlemcili bir makinede), iş parçacıklı bir program aynı anda çoklu iş parçacığı çalıştırmasıdır. Diğer tarafta, eşyordamlar işbirlikçidir:  eşyordamlı bir programda aynı anda eşyordamlardan sadece biri çalışır ve bu çalışan eşyordamlar sadece açık bir askıya alma isteğiyle yürütmeleri askıya alınabilir.


Eşyordam güçlü bir kavramdır. Bu nedenle ana kullanımlarından birkaçı karmaşıktır. Bu bölümdeki örneklerden bazılarını ilk okumanızda anlamazsanız endişelenmeyin. Kitabın geri kalanını okuyabilir ve daha sonra buraya geri gelebilirsiniz. Ama lütfen geri gelin; iyi harcanan zaman olacaktır.


9.1 Eşyordam Temelleri

Lua, coroutine tablosunda eşyordam ile ilgili tüm fonksiyonlarını paketler. create fonksiyonu yeni eşyordamlar oluşturur. Tek argümana sahiptir, eşyordamın yürüteceği kodlanmış bir fonksiyon. Yeni oluşturulan eşyordamı temsil eden thread tip değerini döndürür. Çoğu zaman, create argümanı devamda olduğu gibi anonim bir fonksiyondur:

co = coroutine.create(function () print("selam") end)

print(co) --> thread: 0x8071d98

Bir eşyordam dört farklı durumdan birinde olabilir: askıda, çalışıyor, ölü ve normal. Bir eşyordamı yarattığımızda askıda durumunda başlar. Bu, onu yarattığımızda eşyordamın gövdesini otomatik olarak çalıştırmadığı anlamına gelir. Bir eşyordamın durumunu status fonksiyonuyla kontrol edebiliriz:

print(coroutine.status(co)) --> askıda

coroutine.resume fonksiyonu bir eşyordamın yürütülmesini başlatır(yeniden) ve durumunu askıda 'dan çalışıyor'a dönüştürür:

coroutine.resume(co) --> selam

Bu örnekte eşyordam gövdesi çalışırken basitçe “selam” yazdırır ve sonlanıp eşyordamı ölü durumuna getirir return etmeden:

print(coroutine.status(co)) --> ölü

Şu ana kadar eşyordamlar fonksiyonları çağırmanın karmaşık bir yolundan başka bir şey gibi görünmüyor. Eşyordamların gerçek gücü, çalışan bir eşyordamın daha sonra tekrar başlatılabilecek şekilde kendi yürütülmesini askıya almasına imkan veren yield fonksiyonuyla gelir. Basit bir örnek görelim:

co = coroutine.create(function ()
for i=1,10 do
print("co", i)
coroutine.yield()
end
end)

Şimdi, bu eşyordamı devam ettirdiğimizde yürütmesini başlatıp ilk yield'e kadar çalışır:

coroutine.resume(co) --> co 1


Durumunu kontrol edersek eşyordamın askıya alındığını ve bu nedenle tekrar devam edilebileceğini görebiliriz:

print(coroutine.status(co)) --> askıda

Eşyordamın bakış açısından, askıdayken gerçekleşen tüm faaliyetler yield çağrısının içinde gerçekleşir. Eşyordamı devam ettirdiğimizde bu yield çağrısı akabinde geri döner ve eşyordam bir sonraki yield veya end'ine kadar yürütmesini sürdürür:

coroutine.resume(co) --> co 2
coroutine.resume(co) --> co 3
...
coroutine.resume(co) --> co 10
coroutine.resume(co) -- hiçbirşey yazılmıyor

resume'e son çağrı sırasında eşyordam gövdesi döngüyü tamamlar ve sonra geri döner böylece eşyordam artık ölür. Tekrar devam ettirmeye çalışırsak resume false artı bir hata iletisi döndürür:

print(coroutine.resume(co))
   --> false ölü eşyordam devam ettirilemez

resume korumalı modda çalıştığını unutmayın. Bu nedenle, bir eşyordam içinde herhangi bir hata varsa Lua hata iletisi göstermez ama bunun yerine resume çağrısına onu döndürür.

Bir eşyordam diğerini devam ettirdiği zaman askıya alınmaz; sonuçta biz onu devam ettiremeyiz. Ancak o da çalışmaz çünkü çalışan eşyordam diğeri. Böylece durumu, normal durum olarak adlandırdığımız halde.

Lua'da yararlı bir imkan, resume-yield çifti veri alışverişi olmasıdır.  onu bekleyen karşılık gelen yield'e sahip olmayan, ilk resume, ekstra argümanlarını eşyordam ana fonksiyonuna argümanlar olarak geçer:

co = coroutine.create(function (a,b,c)
print("co", a,b,c)
end)
coroutine.resume(co, 1, 2, 3) --> co 1 2 3

resume çağrısı, hiçbir hatanın olmadığı gösteren true sonrasında karşılık gelen yield'e geçilen tüm argümanları döndürür:

co = coroutine.create(function (a,b)
coroutine.yield(a + b, a - b)
end)
print(coroutine.resume(co, 20, 10)) --> true 30 10

Simetrik olarak yield, karşılık gelen resume'e geçilen tüm ekstra argümanları döndürür:

co = coroutine.create (function ()
print("co", coroutine.yield())
end)
coroutine.resume(co)
coroutine.resume(co, 4, 5) --> co 4 5


Nihayetinde bir eşyordam sona erdiğinde ana fonksiyonu tarafından döndürülen tüm değerler karşılık gelen resume'e gider:

co = coroutine.create(function ()
return 6, 7
end)
print(coroutine.resume(co)) --> true 6 7

Tüm bu imkanları nadiren aynı eşyordamda kullanırız ama hepsinin kullanımları var.

Eşyordamlar hakkında hazırda bazı şeyler bilenler için devam etmeden önce bazı kavramları açıklığa kavuşturmak önemli. Lua, asimetrik eşyordamlar olarak isimlendirdiğim şeyi sunar. Bu, askıya alınmış bir eşyordamı devam ettirmek için farklı bir fonksiyonun olduğu, yürütüleni askıya almak için farklı fonksiyonun olduğu anlamına gelir. Diğer bazı diller herhangi bir eşyordamdan diğerine kontrolu aktarmak için yalnızca bir fonksiyonun bulunduğu simetrik eşyordamlar sunar.

Bazı insanlar asimetrik eşyordama yarı-eşyordam diyor (simetrik olmadıklarına, gerçekten eş değiller). Bununla birlikte diğer bazı insanlar aynı yarı-eşyordam terimini bir eşyordamın yalnızca herhangi bir fonksiyonu çağırmadığında yani kontrol yığınında bekleyen çağrılar olmadığında yürütülmesi askıya alınabilir olan eşyordamların sınırlı bir uygulamasını belirtmek için  kullanıyorlar. Başka bir deyişle sadece bu tür yarı eşyordamların ana gövdesi yield edilebilir. Python'da ki generator, yarı eşyordamın bu anlamda örneğidir.

Simetrik ve asimetrik eşyordamların arasındaki farktan farklı olarak, eşyordamlar ve generatorlar arasındaki fark (Python'da sunulan şekliyle) derindir; generatorlar, full eşyordamlar ile yazabileceğimiz birkaç ilginç yapı uygulamak için yeterince güçlü değildir. Lua, full asimetrik eşyordamlar sunmaktadır. Simetrik eşyordamları tercih edenler Lua'nın asimetrik imkanlarının üstünde onları uygulayabilirler. Bu kolay bir iş. (Basitçe, herbir transfer, bir resume takibinde bir yield yapabilir)

9.2 Hatlar(Pipe) ve Filtreler

Eşyordamların en paradigmatik örneklerinden biri üretici-tüketici problemidir. Süreki değerler üreten bir fonksiyona (örneğin bir dosyadan okuma) ve sürekli bu değerleri tüketen başka bir fonksiyona (örneğin bunları başka bir dosyaya yazma) sahip olduğumuzu varsayalım. Tipik olarak bu iki fonksiyon şuna benzer:

function uretici ()
while true do
local x = io.read()         -- yeni değer üret
send(x)                         -- tüketiciye yolla
end
end

function tuketici ()
while true do
local x = receive()         -- üreticiden al
io.write(x, "\n")             -- yeni değeri tüket
end
end

(Bu uygulamada hem üretici hem de tüketici ikilisi daima çalışır. İşleyecek daha fazla veri olmadığında onları durdurmak üzere değiştirmek kolay.) Burada sorun, alma ile göndermenin nasıl eşleşeceğidir. ana-döngü-sahipliği probleminin tipik bir örneğidir bu. Hem üretici hem de tüketici aktif, her ikisi de kendi ana döngülerine sahip ve her ikisi de diğerinin çağrılabilir bir hizmet olduğunu varsayar. Bu özel örnek için fonksiyonlardan birinin yapısını değiştirmek, döngüsünün kilidini açmak ve pasif bir aktör yapmak kolay. Bununla birlikte bu yapı değişikliği diğer gerçek senaryolarda kolay olmaktan uzak olabilir.

Eşyordamlar üreticileri ve tüketicileri eşleştirmek için ideal bir araç sağlar,  resume-yield çifti, çağıran ve çağrılan arasındaki tipik ilişkiyi ters çevirir. Bir eşyordam yield çağırdığında yeni bir fonksiyona girmez; bunun yerine bekleyen çağrıya döner (devam ettirmek üzere). Benzer şekilde, resume çağrısı yeni bir fonksiyon başlatmaz ancak yield çağrıya döner. Bu özellik, bir gönderimin ve bir alımın, biri efendi ve diğerinin köle gibi davrandığı şekilde eşleştirilmesi için ihtiyacımız olan şeydir. Böylece, alım, yeni bir değer üretebilmesi için üreticiyi devam ettirir ; ve yield'ler yeni değeri tüketiciye geri gönderir :

function receive ()
local status, value = coroutine.resume(producer)
return value
end

function send (x)
coroutine.yield(x)
end

Tabii artık üretici bir eşyordam olmalı:

producer = coroutine.create(
function ()
while true do
local x = io.read() -- yeni değer üret
send(x)
end
end)

Bu tasarımda, program tüketiciyi çağırarak başlar. Tüketici bir öğeye ihtiyaç duyduğunda tüketiciye vermek için bir öğeye sahip olana kadar çalışan üreticiden devam ettirilir ve tüketici tekrar devam ettirilmeye başlatılınca durur. Bu şekilde, tüketici odaklı tasarım dediğimiz şey olur.

Bu tasarımı, veride bir çeşit dönüşüm yapan üretici ve tüketici arasına oturan görevler olan, filtrelerle genişletebiliriz. filtre aynı anda bir tüketici ve  bir üreticidir. böylece yeni değerler almak için üreticiyi devam ettirir ve dönüştürülen değerleri tüketiciye verir(yield eder). Önemsiz bir örnek olarak önceki kodumuza, her satırın başına bir satır numarası koyan bir filtre ekleyebiliriz. Kodu 9.1 listesinde. Son parça, ihtiyaç duyulan bileşenleri basitçe oluşturur, onlara bağlanır ve son tüketiciyi başlatır:


p = producer()
f = filter(p)
consumer(f)


Ya da daha iyisi:

consumer(filter(producer()))

Liste 9.1. filtrelerle üretici–tüketici:
function receive (prod)
local status, value = coroutine.resume(prod)
return value
end

function send (x)
coroutine.yield(x)
end

function producer ()
return coroutine.create(function ()
while true do
local x = io.read() -- yeni değer üret
send(x)
end
end)
end

function filter (prod)
return coroutine.create(function ()
for line = 1, math.huge do
local x = receive(prod) -- yeni değeri al
x = string.format("%5d %s", line, x)
send(x) -- tüketiciye onu yolla
end
end)
end

function consumer (prod)
while true do
local x = receive(prod) -- yeni değeri al
io.write(x, "\n") -- yeni değeri tüket
end
end




Liste 9.2. a'nın ilk n elemanlarının tüm permütasyonları oluşturmak için fonksiyon:
function permgen (a, n)
n = n or #a -- ’n’ için varsayılan ’a’ 'nın büyüklüğü
if  n <= 1 then -- değişim yok?
printResult(a)
else
for i=1,n do
-- sonuncu olarak i-inci elemanı koy
a[n], a[i] = a[i], a[n]
-- diğer elemanların tüm permütasyonlarını üret
permgen(a, n - 1)
-- i-inci eleman eski hale getir
a[n], a[i] = a[i], a[n]
end
end
end



Önceki örneği okuduktan sonra Unix pipeleri aklınıza düşdüyse, yalnız değilsiniz. Sonuçta, eşyordamlar bir (engelsiz) multithreading türü. hatlarla her bir görev ayrı bir process'de (süreç) çalışır, eşyordamlarla her bir görev ayrı bir eşyordamda çalışır. hatlar, yazan (üretici) ve okuyan (tüketici) arasında bir tampon sağlar, bu nedenle göreceli hızlarında bir miktar muafiyet vardır. Bu hatların bağlamında önemli çünkü süreçler arasında geçiş maliyeti yüksektir. Eşyordamlarla görevler arasında geçiş maliyeti çok daha küçüktür (kabaca bir fonksiyon çağrısı ile aynıdır), bu şekilde yazan ve okuyan el ele çalışabilir.

9.3 İteratörler olarak eşyordamlar

Döngü iteratörlerini üretici-tüketici şablonunun belirli bir örneği olarak görebiliriz: bir iteratör, döngü gövdesi tarafından tüketilecek öğeleri üretir. Bu bağlamda, iteratörler yazmak için eşyordamları kullanmak uygun görünüyor. Gerçekten de, eşyordamlar bu görev için güçlü bir araç sağlar. Yine, anahtar özellik, çağıran ve çağrılan arasındaki ilişkiyi ters çevirme olayı. Bu özellik ile iteratöre ardışık çağrılar arasında durumu nasıl tutulacağından endişe etmeden iteratörler yazabiliriz.


Bu tür bir kullanımı göstermek için, verilen bir dizinin tüm permütasyonlarını dolaşmak için bir iteratör yazalım. Direkt böyle bir iteratör yazmak kolay bir iş değil, ancak tüm bu permütasyonları üreten yinelemeli bir fonksiyon yazmak o kadar zor değil. Fikir basit: her bir dizi öğesini son konuma koyup geri kalan elemanların tüm permütasyonlarını yinelemeli olarak oluştur. Kodumuz 9.2 listesinde. Çalışmaya koymak üzere uygun bir printResult fonksiyonu tanımlamalı ve uygun argümanlarla permgen'i çağırmalıyız:


function printResult (a)
for i = 1, #a do
io.write(a[i], " ")
end
io.write("\n")
end

permgen ({1,2,3,4})
  --> 2 3 4 1
  --> 3 2 4 1
  --> 3 4 2 1
     ...
  --> 2 1 3 4
  --> 1 2 3 4

üreteç hazır olduktan sonra bir iteratöre onu dönüştürmek otomatik bir görev. İlk olarak, printResult'u yield 'e çeviririz:

function permgen (a, n)
n = n or #a
if  n <= 1 then
coroutine.yield(a)
else
<önceki gibi>

Ardından, üretecin bir eşyordam içinde çalışmasını ve ardından iteratör fonksiyonunu oluşturmasını sağlayan bir fabrika tanımlarız. iteratör basitçe bir sonraki permütasyonu üretmek için eşyordamı devam ettirir:


function permutations (a)
local co = coroutine.create(function () permgen(a) end)
return function () -- iterator
local code, res = coroutine.resume(co)
return res
end
end

Bu yerinde mekanizma ile , bir dizinin tüm permütasyonlarını for deyimi ile iterate etmek tırıvırı:


for p in permutations{"a", "b", "c"} do
printResult(p)
end
  --> b c a
  --> c b a
  --> c a b
  --> a c b
  --> b a c
  --> a b c

permutations fonksiyonu bir fonksiyon içinde karşılık gelen eşyordamıyla devam ettirme çağrısının paketlendiği Lua daki yaygın şablonu kullanır. Bu şablon o kadar yaygındır ki Lua bunun için özel bir fonksiyon sağlar: coroutine.wrap.  create gibi, wrap yeni bir eşyordam oluşturur. create'in aksine, wrap eşyordamı kendisine döndürmez; bunun yerine çağrıldığında eşyordamı devam ettiren bir fonksiyon döndürür. Orjinal resume'nin aksine bu fonksiyon ilk sonucu olarak bir hata kodu döndürmez; bunun yerine hata durumunda hatayı yükseltir. wrap kullanarak aşağıdaki gibi permutations'ı yazabilirsiniz:

function permutations (a)
return coroutine.wrap(function () permgen(a) end)
end

Genellikle, coroutine.wrap'ı kullanmak coroutine.create'den daha kolaydır.Bize bir eşyordam da tam olarak ihtiyacımız olanı verir: onu devam ettirecek fonksiyon. Ancak, aynı zamanda daha az esnektir. wrap ile oluşturulan bir eşyordamın durumunu kontrol etmenin bir yolu yok. Ayrıca, çalışma zamanı hatalarını kontrol edemeyiz.

9.4 Bloksuz Multithreading

Daha önce de gördüğümüz üzere, eşyordamlar bir çeşit işbirlikçi multithreading'e izin verir. Her bir eşyordam bir iş parçacığına eşdeğerdir. bir çift yield–resume , bir iş parçacığından diğerine  kontrolu değiştirir. Bununla birlikte, klasik multithreading aksine, eşyordamlar bloklu değildir. Bir eşyordam çalışırken dışarıdan durdurulamaz. Açık  istekler(yield çağrısı ile) sadece çalışmayı askıya alır. Bazı uygulamalar için bu bir sorun değil, tam tersine. Programlama preemption(hazırlılık) olmadığında çok daha kolaydır. Senkronizasyon hataları hakkında paranoyak olmanız gerekmez çünkü iş parçacıkları arasındaki tüm senkronizasyon programda açıktır. Eşyordamın yalnızca kritik bölgenin(critical region) dışında yield edildiğinden sadece emin olmanız gerekir.


Bununla birlikte, bloksuz multithreading ile herhangi bir iş parçacığı engelleme işlemi çağrıldığında,
işlem tamamlanana kadar tüm program engellenir. Çoğu uygulama için, bu, birçok programcının bu geleneksel multithreading'e gerçek bir alternatif olarak eşyordamları göz ardı etmesine  neden olan, kabul edilemez bir davranış. Burada göreceğimiz üzere, bu sorunun ilginç (ve açık, geç anlaşılan) bir çözümü var.

Tipik bir multithreading  durumu varsayalım: HTTP ile birkaç uzak dosyayı indirmek istiyoruz. Tabii ki, birkaç uzak dosyayı indirmek için, bir uzak dosyayı nasıl indireceğimizi bilmeliyiz. Bu örnekte, Diego Nehab tarafından geliştirilen LuaSocket kütüphanesini kullanacağız. Bir dosyayı indirmek için, siteye bir bağlantı açmalı, dosyaya bir istek göndermeli, dosyayı (bloklarla) almalı ve bağlantıyı kapatmalıyız. Lua'da bu görevi aşağıdaki gibi yazabiliriz. İlk olarak LuaSocket kütüphanesini yüklüyoruz:

require "socket"

Ardından indirmek istediğimiz dosyayı(file) ve ana bilgisayarı(host) tanımlarız. Bu örnekte, World Wide Web Consortium (W3C) sitesinden HTML 3.2 referans tanımlamasını indireceğiz:

host = "www.w3.org"
file = "/TR/REC-html32.html"

Daha sonra, bu sitenin 80 numaralı bağlantı noktasına-portuna (HTTP bağlantıları için standart bağlantı noktası) bir TCP bağlantısı açıyoruz:

c = assert(socket.connect(host, 80))

Bu işlem, dosya istemi göndermek için kullandığımız bir bağlantı nesnesi döndürür:

c:send("GET " .. file .. " HTTP/1.0\r\n\r\n")

Ardından, dosyayı 1 Kilobaytlık bloklarla okur ve her bloğu standart çıktıya yazdırırız:

while true do
local s, status, partial = c:receive(2^10)
io.write(s or partial)
if status == "closed" then break end
end

receive fonksiyonu,  okuduğu stringi veya hata durumunda nil döndürür; ikinci durumda bir hata kodu (status) ve hata olana kadar okuduğu şeyi(partial)  döndürür. host bağlantıyı kapattığında kalan girdiyi yazdırır ve alma döngüsünü break ile kırırarız.

Dosyayı indirdikten sonra bağlantıyı kapatıyoruz.

c:close()

Şimdi bir dosyayı nasıl indireceğimizi bildiğimize göre birkaç dosya indirme sorununa dönelim.
küçük yaklaşım, bir seferde bir tane indirmektir. Ancak, bir dosyayı yalnızca bir öncekini bitirdikten sonra okumaya başladığımız bu sıralı yaklaşım çok yavaş. Uzak bir dosya okurken, bir program çoğu zaman veri gelmesini beklerken harcanır. Daha spesifik olarak, receive çağrısında engellenerek zamanının çoğunu harcıyor. Dolayısıyla, program aynı anda tüm dosyaları indirse çok daha hızlı çalışabilirdi. Daha sonra  bağlantıda hazırda veri yokken program başka bir bağlantıdan okuyabilir. Daha açık olarak, eşyordamlar bu eşzamanlı indirmeleri oluşturmak için uygun bir yol sunar. Her indirme görevi için yeni bir iş parçacığı oluştururuz. Bir iş parçacığı kullanılabilir veriye sahip  olmadığında başka bir iş parçacığı çağıran basit bir dağıtıcıya kontrolu yield eder.

Programı eşyordamlarla yeniden yazmak için önce önceki indirme kodunu bir fonksiyon olarak yeniden yazıyoruz. Sonuç 9.3 listesinde. Uzak dosya içeriğiyle ilgilenmediğimizden bu fonksiyon dosyayı standart çıktıya yazmak yerine dosya boyutunu sayar ve yazdırır. (Birkaç dosya okuyan birkaç iş parçacıklıda, çıktı tüm dosyaları birbirine karıştıracaktı.).

Liste 9.3. bir web sayfasını yükleyen fonksiyon:
function download (host, file)
local c = assert(socket.connect(host, 80))
local count = 0 -- okunan bayt sayısı say
c:send("GET " .. file .. " HTTP/1.0\r\n\r\n")
while true do
local s, status, partial = receive(c)
count = count + #(s or partial)
if status == "closed" then break end
end
c:close()
print(file, count)
end


Bu yeni kodda, bağlantıdan veri almak için yardımcı bir fonksiyon (recieve) kullanıyoruz. Sıralı yaklaşımda kodu şöyle olacaktır:

function receive (connection)
return connection:receive(2^10)
end

Eşzamanlı uygulama için bu fonksiyon engellenmeden veri almalıdır. yeterli veri yoksa yield eder. Yeni kod şöyle:


function receive (connection)
connection:settimeout(0) -- bloklama
local s, status, partial = connection:receive(2^10)
if status == "timeout" then
coroutine.yield(connection)
end
return s or partial, status
end


settimeout(0) çağrısı, bloksuz işlem bağlantısı üzerinden tüm operasyonu yapar. İşlem durumu “timeout" olduğunda, işlemin tamamlanmadan dönmesi anlamına gelir. Bu durumda, thread yield eder. yield'e false'den farklı argüman , sevk ediciye, iş parçacığının hala görevini sürdürdüğünü belirtmek için geçilir. Bir zaman aşımı durumunda bile bağlantı partial değişkeninde zaman aşımına kadar okuduğunu döndürür dikkat.

Liste 9.4 sevk ediciyi artı yardımcı kodu gösterir. threads tablosu sevk edici için tüm canlı iş parçacıklarının bir listesini tutar. get fonksiyonu her indirme işleminin tek bir iş parçacığında çalışmasını sağlar. sevk edicinin kendisi esas olarak tüm iş parçacıklarını dolaşan, tek tek devam ettiren bir döngüdür. Ayrıca, görevlerini tamamlayan threadları listeden kaldırması gerekir. Çalıştırmak için daha fazla iş parçacığı olmadığında döngüyü durdurur.

Liste 9.4. sevk edici:
threads = {} -- yaşayan tü threadların listesi

function get (host, file)
-- eşyordam yarat
local co = coroutine.create(function ()
download(host, file)
end)
-- listeye onu ekle
table.insert(threads, co)
end

function dispatch ()
local i = 1
while true do
if threads[i] == nil then -- başka thread yok mu?
if threads[1] == nil then break end -- liste boş mu?
i = 1 -- döngüyü yeniden başlat
end
local status, res = coroutine.resume(threads[i])
if not res then -- thread görevini bitirdi mi?
table.remove(threads, i)
else
i = i + 1
end
end
end


Son olarak, ana program ihtiyaç duyduğu threadları oluşturur ve sevk ediciyi çağırır. Örneğin, W3C sitesinden dört belge indirmek için ana program şöyle olabilir:

host = "www.w3.org"

get(host, "/TR/html401/html40.txt")
get(host, "/TR/2002/REC-xhtml1-20020801/xhtml1.pdf")
get(host, "/TR/REC-html32.html")
get(host, "/TR/2000/REC-DOM-Level-2-Core-20001113/DOM2-Core.txt")

dispatch() -- ana döngü


Benim bilgisayarda eşyordamları kullanarak bu dört dosyayı indirmem altı saniye sürdü. Sıralı uygulama ile bu sürenin iki katından fazla sürer (15 saniye). 

Bu hıza rağmen, bu son uygulama optimal olmaktan çok uzak.  bir iş parçacığının okuması gereken en az bir şey varken her şey yolunda gider. Ancak, thread'ın okunacak veriye sahip olmadığında , sevk edici meşgulde bekleme yapar, sadece okunacak veriye sahip olup olmadıklarını kontrol için bir threaddan diğerine gider. Sonuç olarak, bu eşyordam uygulaması sıralı çözümden yaklaşık 30 kat daha fazla CPU kullanır.

Bu davranıştan kaçınmak için LuaSocket'ten select fonksiyonunu kullanabiliriz. Bu, bir soket grubunda bir durum değişikliği beklerken bir programı bloklamaya imkan verir. Uygulamamızdaki değişiklikler küçük. sadece sevk ediciyi değiştirmek zorundayız; yeni versiyon Liste 9.5 'de.

Liste 9.5. select kullanarak sevk edici:
function dispatch ()
local i = 1
local connections = {}
while true do
if threads[i] == nil then -- başka thread yok mu?
if threads[1] == nil then break end
i = 1 -- döngüyü yeniden başlat
connections = {}
end
local status, res = coroutine.resume(threads[i])
if not res then -- thread görevini bitirdi mi?
table.remove(threads, i)
else -- time out
i = i + 1
connections[#connections + 1] = res
if #connections == #threads then -- tüm threadlar bloklu mu?
socket.select(connections)
end
end
end
end

döngü boyunca, bu yeni sevk edici connections tablosunda zaman aşımı bağlantıları toplamaktadır. Unutmayın ki, yield'e bu tür bağlantıları geçirir recieve; böylece resume onları döndürür. Tüm bağlantılar zaman aşımına uğrarsa, sevk edici, bu bağlantılardan herhangi birinin durumunu değiştirmesini beklemek için select'i çağırır. Bu son uygulama eşyordamlar ile ilk uygulama kadar hızlı çalışır. Dahası, meşgul bekleme(busy wait) olmadığı için, sıralı uygulamadan az birazcık fazla CPU kullanır.


Hiç yorum yok:

Yorum Gönder