30 Thread(İş Parçacığı) ve State(Durum)

30 Thread(İş Parçacığı) ve State(Durum)

Lua gerçek multithreading'i desteklemez, misal threadların bloklu bellek paylaşımı. Bu destek eksikliğinin iki nedeni var: ilk sebep ANSI C'nin sağlamaması ve bu nedenle bu mekanizmayı Lua'da uygulamak için taşınabilir bir yol yok. İkinci ve daha güçlü sebep, multithreading 'in Lua için iyi bir fikir olduğunu düşünmüyoruz.

Multithreading düşük seviyeli programlama için geliştirilmiştir. Semaforlar ve monitörler gibi senkronizasyon mekanizmaları işletim sistemleri(ve usta programcılar ) çerçevesinde tasarlanmıştır , klasik uygulama programları için değil . Multithreading ile ilgili hataları bulmak ve düzeltmek çok zordur ve bu hataların bazıları güvenlik ihlallerine yol açabilir. Dahası multithreading kullanımı bir programın bazı kritik bölümlerinde, misal bellek ayırıcısında , senkronizasyon ihtiyacıyla ilgili performans cezalarına neden olabilir.

Multithreading ile ilgili sorunlar, bloklu iş parçacıkları ve bellek paylaşımı kombinasyonundan kaynaklanır, velhasıl bellek paylaşımı olmadan veya bloklu olmayan iş parçacıkları kullanarak bu sorunlardan kaçınabiliriz. Lua her ikisi için de destek sunar. Lua threadları (namı diğer coroutine'ler-eşyordamlar) işbirlikçi ve bu nedenle öngörülemeyen iş parçacığı geçişleri ile oluşan sorunları önler. Lua state'leri bellek paylaşmaz ve bu nedenle Lua'da eşzamanlılık için iyi bir zemin oluşturur. Bu bölümde her iki seçeneği de ele alacağız.

30.1 Çoklu Threadlar

Thread, Lua'daki coroutine'in özüdür. Bir coroutine'i thread artı güzel bir arayüz olarak düşünebiliriz ya da bir thread'ı  düşük seviyeli API'li bir coroutine olarak düşünebiliriz.

C API açısından bir thread'ı bir stack-yığın olarak düşünülmesi yararlı olabilir — ki uygulama açısından aslında bu böyledir. Her bir yığın, bir iş parçacığının bekleyen çağrıları ve her çağrının parametreleri ve yerel değişkenleri hakkında bilgi tutar. Başka bir deyişle bir yığın bir iş parçacığının çalışmaya devam etmesi için gereken tüm bilgilere sahiptir. Velhasıl, birden çok iş parçacığı birden çok bağımsız yığın anlamına gelir.

Lua-C API'den çoğu fonksiyon çağrımızda bu fonksiyon özgün yığın üzerinde çalışır. Örneğin, lua_pushnumber özgün bir yığına sayı push eder-koyar; lua_pcall yığını çağırması gerekir. Lua hangi yığının kullanılacağını nasıl bilir? Farklı bir yığın üzerine sayı koymak için ne yaparız? Keramet,  bu fonksiyonların ilk argümanı, lua_State tipi, yalnızca Lua durumunu değil aynı zamanda bu durum içindeki iş parçacığını temsil eder.

Bir Lua durumu oluşturduğunuzda Lua otomatik olarak bu durum için ana iş parçacığı(main thread) olarak adlandırılan yeni bir iş parçacığı oluşturur. Ana thread asla çöp toplamaya tabi tutulmaz. lua_close ile durumu kapattığınızda state(durum) ile birlikte serbest bırakılır.

lua_newthread çağrısıyla bir state için başka bir iş parçacığı oluşturabilirsiniz:

lua_State *lua_newthread (lua_State *L);

Bu fonksiyon, yeni iş parçacığını temsil eden bir lua_State işaretçisi döndürür ve ayrıca "thread" tipinde bir değer olarak yığına yeni iş parçacığını koyar. Örneğin şu deyimden sonra

L1 = lua_newthread(L);

içsel olarak ikisi de aynı Lua durumuna başvuran , L1 ve L,  iki iş parçacığımız olur.
Her iş parçacığı kendi yığınına sahiptir. L1 yeni iş parçacığı  boş bir yığın ile başlar; L eski iş parçacığı yığınının üstünde yeni iş parçacığına sahiptir:

printf("%d\n", lua_gettop(L1)); --> 0
printf("%s\n", luaL_typename(L, -1)); --> thread

Ana iş parçacığı hariç, iş parçacıkları diğer Lua nesneleri gibi çöp toplamaya tabidir. Yeni bir iş parçacığı oluşturduğunuzda, yığına push edilen değer iş parçacığının çöp olmamasını sağlar. Asla, state'de doğru şekilde bağlanmamış bir thread kullanmamalısınız. (Ana thread içsel olarak bağlanır bu nedenle endişelenmenize gerek yok.) Lua API'e yapılan herhangi bir çağrı , hatta bu iş parçacığını kullanan bir çağrı bile, bağlanmamış iş parçacığını toplayabilir. Örneğin aşağıdaki kod parçasını ele alalım:

lua_State *L1 = lua_newthread (L);
lua_pop(L, 1); /* L1 artık Lua için çöp */
lua_pushstring(L1, "hello");

lua_pushstring çağrısı, L1 kullanımda olmasına rağmen çöp toplayıcıyı tetikleyebilir ve L1  toplanabilir(uygulamayı çökertebilir). Bunu önlemek için her zaman kullandığınız iş parçacıklarına referansı tutun (örneğin bir bağlı  iş parçacığı yığınında veya kaydında) .

Yeni bir threada sahip olduktan sonra ana thread gibi onu kullanabiliriz. stackına öğeler push edip pop edebiliriz, fonksiyonları çağırmada onu kullanabiliriz, vb. Örneğin aşağıdaki kod yeni iş parçacığında f(5) çağrısını yapar ve sonucu eski iş parçacığına taşır:

lua_getglobal(L1, "f"); /* 'f' global bir fonksiyon olduğu varsayılmakta */
lua_pushinteger(L1, 5);
lua_call(L1, 1, 1);
lua_xmove(L1, L, 1);

lua_xmove fonksiyonu Lua değerlerini iki yığın arasında taşır. lua_xmove(F,T,n) gibi bir çağrı, n tane öğeyi F yığınıdan çıkarır-pop eder ve T'e koyar-push eder.

Tabi bu tür kullanımlar için yeni bir thread'a ihtiyacımız yok; ana thread'ı da kullanabilirdik.
çoklu iş parçacığı kullanımın ana noktası coroutine'leri uygulamaktır, ki daha sonra devam edecek yürütmelerini askıya alabiliyoruz . Bunun için lua_resume fonksiyonuna ihtiyacımız var:

int lua_resume (lua_State *L, int narg);

Bir coroutine çalıştırmaya başlatmak için lua_pcall'ı kullandığımız gibi lua_resume'i kullanıyoruz: çağrılacak fonksiyonu push eder, argümanlarını push eder ve narg'de argümanların sayısını geçerek lua_resume'i çağırırız. Davranış yine lua_pcall'a üç farkla çok benzer. İlk olarak, lua_resume istenen sonuç sayısı için bir parametreye sahip değil; daima tüm sonuçları çağrılan fonksiyondan döndürür. İkincisi, bir hata işleyicisi için bir parametreye sahip değil;  hata yığını bozmaz böylece hatadan sonra yığını inceleyebilirsiniz. Üçüncü olarak, çalışan fonksiyon yol verirse(yield - işin bitimi veya tamamlanması değil de belli çalışma sonrası durup diğerine yol verme) lua_resume özel bir kod , LUA_YIELD, döndürür ve iş parçacığını daha sonra devam ettirilebilecek bir durumda bırakır.

lua_resume LUA_YIELD döndürdüğünde iş parçacığının yığınının görünür kısmı yalnızca yol vermede geçilen değerleri içerir. lua_gettop çağrısı yol verme değerlerinin sayısını döndürür. Bu değerleri başka bir iş parçacığına taşımak için lua_xmove kullanabilirsiniz.

Askıya alınmış bir iş parçacığını devam ettirmek için tekrar lua_resume'ı  çağırırız. Bu tür çağrılarda,
Lua yığındaki tüm değerlerin yol verme çağrısıyla döndürüleceğini varsayar. Özgün olarak, lua_resume'den dönüş ile akabindeki devam ettirme arasında iş parçacığının yığınına dokunmazsanız yol verme yol verdiği değerleri aynen döndürür.

Tipik olarak, eşyordamı gövdesi olarak bir Lua fonksiyonuyla başlatırız. Bu Lua fonksiyonu diğer Lua fonksiyonlarını çağırabilir ve bu fonksiyonlardan herhangi biri nihayetinde lua_resume çağrısıyla biterek, yol verebilir.  Örneğin, aşağıdaki tanımlamalara sahip olduğumuzu varsayalım:

function foo (x) coroutine.yield(10, x) end

function foo1 (x) foo(x + 1); return 3 end

Şimdi, şu C kodunu çalıştırıyoruz:

lua_State *L1 = lua_newthread(L);
lua_getglobal(L1, "foo1");
lua_pushinteger(L1, 20);
lua_resume(L1, 1);

lua_resume çağrısı, iş parçacığının yol verildiğini işaret etmek için LUA_YIELD döndürür. Bu noktada, L1 yığını yol vermeye verilen değerlere sahiptir:

printf("%d\n", lua_gettop(L1)); --> 2
printf("%d\n", lua_tointeger(L1, 1)); --> 10
printf("%d\n", lua_tointeger(L1, 2)); --> 21

İş parçacığını tekrar devam ettirdiğimizde durduğu yerden devam eder (yol vermeye çağrıdan).  Oradan, foo foo1'e döner , ki o da lua_resume'e döner:

lua_resume(L1, 0);
printf("%d\n", lua_gettop(L1)); --> 1
printf("%d\n", lua_tointeger(L1, 1)); --> 3

lua_resume 'e bu ikinci çağrı normal bir dönüş anlamına gelen 0 döndürecek.

Bir coroutine aynı zamanda C fonksiyonları da çağırabilir. Mamafih, C'de programlama yaparken doğal bir soru şudur: Bir C fonksiyonundan yol verme mümkün müdür?

Standart Lua, C fonksiyon çağrıları üzerinden yol veremez. (Lua'ya buna izin veren bazı ilginç yamalar var. Ancak taşınabilir olmayan kod kullanılması , assembly olarak küçük parçalar dahil edilmesi gerekir). Bu kısıtlama bir C fonksiyonunun kendini askıya alamayacağını gösterir. Bir C fonksiyonunun yol verme için tek yolu dönüş yapması yani kendisini askıya alamayan ancak bir Lua fonksiyonu olması gereken çağırana dönmesi. çağıranın askıya alınması için olan C fonksiyonu devamdaki şekilde lua_yield'i çağırmalıdır:

return lua_yield(L, nres);

Burada nres, ilgili devam ettirmeyle döndürülecek yığının üstündeki değerlerin sayısıdır. İş parçacığı yeniden devam ettiğinde, çağıran devam ettirmeye verilen değerleri alır.

C fonksiyonlarının yol verme kısıtlamasını Lua'da bir döngü içinde onları çağırarak aşabiliriz. Bu yöntemde, fonksiyon yol verme ve iş parçacığı devam ettirme sonrası döngü  fonksiyonu tekrar çağırır. Örnek olarak, bazı verileri okuyan ve veriler mevcut değilken yol veren bir fonksiyon kodlamak istediğimizi varsayalım. Fonksiyonu devamdaki gibi C'de yazabiliriz:


int prim_read(lua_State *L){
  if (nothing_to_read())
    return lua_yield(L, 0);
  lua_pushstring(L, read_some_data());
  return 1;
}

Fonksiyonun okuması gereken bazı verileri varsa, bu verileri okur ve döndürür. Aksi takdirde yol verir. İş parçacığı devam ettirildiği zaman ancak prim_read'e dönmez, çağırana döner.

Şimdi, çağıranın prim_read 'i devamdaki gibi bir döngüde çağırdığını varsayalım:

function read()
    local line
    repeat
        line = prim_read()
    until line
    return line
end

prim_read yol verdiği zaman iş parçacığı askıya alınır. Devam ettiğinde, prim_read'ın dönüş noktasından devam eder, bu da line'e atamadır. Atanan gerçek değer, devam ettirmeye verilen değer olacaktır. Hiçbir değer verilmediğini varsayarsak, line nil alır ve döngü devam eder, prim_read tekrar çağrılır. Bazı veriler okunana veya devam ettirmeye nil'den farklı bir değer geçilene kadar tüm süreç kendini tekrarlar.

30.2 Lua Durumları

luaL_newstate'e her çağrı (veya lua_newstate, Bölüm 31'de göreceğimiz üzere) yeni bir Lua durumu oluşturur. Farklı Lua durumları birbirinden tamamen bağımsızdır. Hiçbir veri paylaşmazlar. Bu, bir Lua durumunda ne olursa olsun diğer bir Lua durumunu bozamaz demektir. Bu aynı zamanda Lua durumları doğrudan iletişim kuramaz anlamına gelir; Birkaç aracı C kodu kullanmak zorundayız. Örneğin L1 ve L2 iki durum  olmak üzere, devamdaki komut  L1'deki yığının üstündeki stringi L2'e push eder:

lua_pushstring(L2, lua_tostring(L1, -1));

Veri C'den geçilmesi gerektiğinden Lua durumları yalnızca string ve sayı gibi C'de temsil edilebilir tiplerle alışverişi yapılabilir.

multithreading kullanılan sistemlerde, bunları Lua ile kullanmanın ilginç bir yolu, her iş parçacığı için bağımsız bir Lua durumu oluşturmaktır. Bu mimari, paylaşımlı bellek olmadan eşzamanlılığa sahip olduğumuz Unıx process'lerine benzer iş parçacıklarıyla sonuçlanır. Bu bölümde, bu yaklaşımı takip eden multithreading  kullanımı için bir prototip uygulaması geliştireceğiz. Bu uygulama için POSIX threads (pthreads) kullanacağım. Sadece temel imkanlar kullanıldığı için kodu diğer thread sistemlerine port etmek zor olmamalı.

Geliştireceğimiz sistem çok basit. Temel amacı,  multithreading  bağlamında birden fazla Lua durumunun kullanımını göstermektir. tertipleyip çalışır hale getirdikten sonra üzerine daha gelişmiş özellikler ekleyebilirsiniz. Kütüphanemizi lproc olarak isimlendireceğiz. Sadece dört fonksiyon sunar:

lproc.start(chunk),  verilen chunk'ı(bir string) çalıştırmak için yeni bir process başlatır. Bir Lua process'i bir C iş parçacığı artı ilişkilendirilmiş bir Lua durumu olarak uygulanır.

lproc.send(channel,val1,val2,...), verilen tüm değerleri (stringler olmalı) verilen kanala gönderir (adıyla tanımlanır, ayrıca bir stringdir).

lproc.receive(channel), verilen kanala gönderilen değerleri alır.

lproc.exit(), process'i bitirir. Sadece ana process'in bu fonksiyona ihtiyacı vardır. Bu process lproc.exit çağrılmadan sonlanırsa tüm program diğer process'lerin sonlanmasını beklemeden sonlandırılır.

Kanallar sadece gönderenler ve alıcılarla eşleştirmede kullanılan stringlerdir. Bir send işlemi, eşleşen receive işlemi ile döndürülen herhangi sayıda string değerleri gönderebilir. Tüm iletişim senkronizedir: bir process'in bir kanala mesaj göndermesi  bu kanaldan alım yapan bir process var olduğu sürece bloklanır, ki bir process'in bir kanaldan alım yapması onu gönderen bir process var olduğu sürece bloklanır.

Sistemin arayüzü gibi uygulama da basittir. biri ileti almak için bekleyen processler için , ve diğeri  ileti  göndermek için bekleyen processler  için olmak üzere sirküler iki tane çift bağlı liste kullanır. Bu listelere erişimi kontrol etmek için tek bir mutex kullanır. Her process'in ilişkili bir koşul değişkeni vardır. Bir process bir kanala bir mesaj göndermek istediğinde o kanalı bekleyen process'i arayan alım listesini dolaşır. bulursa, process'i bekleme listesinden kaldırır, mesajın değerlerini kendinden bulunan process'e taşır ve diğer process'e işaret çakar. Aksi takdirde gönderme listesine kendini ekler ve koşul değişkenini bekler. Bir mesaj almak için simetrik bir işlem yapar.

Uygulamadaki ana öğe process'i temsil eden yapıdır:

struct Proc{
  lua_State *L;
  pthread_t thread;
  pthread_cond_t cond;
  const char *channel;
  struct Proc *previous, *next;
} Proc;

İlk iki alan process tarafından kullanılan Lua durumunu ve process'i çalıştıran C thread'ını temsil eder. Diğer alanlar sadece process bir gönderim/alım eşleşmesi beklemek zorunda olduğunda kullanılır. Üçüncü alan, cond, iş parçacığının kendini bloklamak için kullandığı koşul değişkenidir;
dördüncü alan, process'in beklediği kanalı depolar;  son iki alan, previous ve next, process'i bekleme listesine bağlamak için kullanılır.

İki bekleme listesi ve ilişkili mutex aşağıdaki gibi deklare edilir:

static Proc *waitsend = NULL;
static Proc *waitreceive = NULL;

static pthread_mutex_t kernel_access = PTHREAD_MUTEX_INITIALIZER;

Her process'in bir Proc yapısına ihtiyacı vardır ve scripti send veya receive 'ı çağırdığında yapısına erişmesi gerekir.  Bu fonksiyonların aldığı tek parametre process’in Lua durumudur; yani her process Lua durumunun içinde Proc yapısını depolar, kayıtta full kullanıcı verisi olarak örnekleme için. Bizim uygulamada her durum, “_SELF " anahtarı ile ilişkilendirilmiş olarak, kayıtta karşılık gelen Proc yapısını tutar.

Liste 30.1. bir kanal için bekleyen processi arayan fonksiyon:

static Proc *searchmatch(const char *channel, Proc **list){
  Proc *node = *list;
  if (node == NULL) return NULL; /* liste boş? */
  do {
    if (strcmp(channel, node->channel) == 0) { /* eşleşti? */
      /* list den node'ı kaldır */
      if (*list == node) /* bu node ilk öğemi? */
        *list = (node->next == node) ? NULL : node->next;
      node->previous->next = node->next;
      node->next->previous = node->previous;
      return node;
    }
    node = node->next;
  } while (node != *list);
  return NULL; /* eşleşen yok */
}

getself fonksiyonu verilen bir durum ile ilişkilendirilmiş Proc yapısını alır:

static Proc *getself(lua_State *L){
  Proc *p;
  lua_getfield(L, LUA_REGISTRYINDEX, "_SELF");
  p = (Proc *)lua_touserdata(L, -1);
  lua_pop(L, 1);
  return p;
}

Bir sonraki fonksiyon, movevalues,  gönderici process'inden alıcı process'ine değerler taşır:

static void movevalues(lua_State *send, lua_State *rec){
  int n = lua_gettop(send);
  int i;
  for (i = 2; i <= n; i++) /* alıcıya değerleri taşı */
    lua_pushstring(rec, lua_tostring(send, i));
}

Gönderen yığınındaki tüm değerleri alıcıya taşır tabi ilkin, kanal.

Liste 30.1 verilen bir kanalı bekleyen process'i arayan bir bekleme listesini dolaşan searchmatch'i tanımlar. Bulursa, fonksiyon process'i listeden kaldırır ve onu döndürür; aksi takdirde fonksiyon NULL döndürür.


Liste 30.2. process'i bekleme listesine ekleyen fonksiyon:

static void waitonlist(lua_State *L, const char *channel,
                                           Proc **list){
  Proc *p = getself(L);

  /* listenin sonuna kendini bağla */
  if (*list == NULL)  { /* liste boş? */
    *list = p;
    p->previous = p->next = p;
  }
  else  {
    p->previous = (*list)->previous;
    p->next = *list;
    p->previous->next = p->next->previous = p;
  }

  p->channel = channel;

  do  { /* koşul değişkenini bekle */
    pthread_cond_wait(&p->cond, &kernel_access);
  } while (p->channel);
}

Liste 30.2'de tanımlanan son yardımcı fonksiyon,  process bir eşleşme bulamadığında çağrılır. Bu durumda process kendisini özgül bekleme listesinin sonuna bağlar ve diğer bir process onunla eşleşene ve uyanana kadar bekler: (pthread_cond_wait çevresindeki döngü POSIX threads'da salınan yapay uyandırmalardan korur.) Bir process diğerini uyandırdığında diğer process'in channel alanını NULL olarak set eder. Yani p->channel NULL değilse p process ile hiç eşleşmenin olmadığı anlamına gelir ki bu nedenle beklemeye devam etmesi gerekir.

Yerinde bu yardımcı fonksiyonlarla send-gönderim ve receive-alım yazabiliriz (Liste 30.3). send fonksiyonu channel'i kontrol ederek başlar. Sonra mutex'i kilitler ve eşleşen bir alıcı arar. Bulursa, değerlerini bu alıcıya taşır, alıcıyı hazır olarak işaretler ve uyandırır.  Aksi takdirde kendini beklemede tutar. İşlemi tamamlandığında mutex'in kilidini açar ve Lua'a değersiz döner. receive fonksiyonu benzerdir ancak alınan tüm değerleri döndürmek zorundadır.

Şimdi yeni process'lerin nasıl oluşturulacağını görelim. Yeni process'in yeni iş parçacığına ihtiyacı vardır ve yeni bir iş parçacığının bir gövdeye ihtiyacı vardır. Bu gövdeyi daha sonra tanımlayacağız; devamdaki POSIX threads tarafından belirlenen prototipi:

static void *ll_thread (void *arg);

Liste 30.3. mesaj alıp gönderen fonksiyonlar:

static int ll_send(lua_State *L){
  Proc *p;
  const char *channel = luaL_checkstring(L, 1);

  pthread_mutex_lock(&kernel_access);

  p = searchmatch(channel, &waitreceive);

  if (p)  { /* eşleşen alıcı bulundu? */
    movevalues(L, p->L); /* alıcıya değerleri taşı */
    p->channel = NULL; /* alıcıyı beklenmeyecek olarak işaretle */
    pthread_cond_signal(&p->cond); /* uyandır*/
  }
  else
    waitonlist(L, channel, &waitsend);

  pthread_mutex_unlock(&kernel_access);
  return 0;
}

static int ll_receive(lua_State *L){
  Proc *p;
  const char *channel = luaL_checkstring(L, 1);
  lua_settop(L, 1);

  pthread_mutex_lock(&kernel_access);

  p = searchmatch(channel, &waitsend);

  if (p)  { /* eşleşen gönderici bulundu? */
    movevalues(p->L, L); /* göndericiden değerleri al */
    p->channel = NULL; /* göndericiyi beklenmeyecek olarak işaretle */
    pthread_cond_signal(&p->cond); /* uyandır */
  }
  else
    waitonlist(L, channel, &waitreceive);

  pthread_mutex_unlock(&kernel_access);

  /* tüm stack değerlerini döndür tabi kanal */
  return lua_gettop(L) - 1;
}

Liste 30.4. yeni process'ler yaratan fonksiyon:

static int ll_start(lua_State *L){
  pthread_t thread;
  const char *chunk = luaL_checkstring(L, 1);
  lua_State *L1 = luaL_newstate();

  if (L1 == NULL)
    luaL_error(L, "unable to create new state");

  if (luaL_loadstring(L1, chunk) != 0)
    luaL_error(L, "error starting thread: %s",
               lua_tostring(L1, -1));

  if (pthread_create(&thread, NULL, ll_thread, L1) != 0)
    luaL_error(L, "unable to create new thread");

  pthread_detach(thread);
  return 0;
}

Yeni process oluşturmak ve çalıştırmak için sistem yeni bir Lua durumu oluşturmalı, yeni iş parçacığını başlatmalı, verilen chunk'ı derlemeli, chunk çağrılmalı ve nihayet kaynakları boşaltmalıdır. Orijinal iş parçacığı ilk üç görevi yapar ve yeni iş parçacığı geri kalanını yapar. (Hata işlemesini basitleştirmek için sistem yalnızca verilen chunk'ı başarıyla derledikten sonra yeni iş parçacığını başlatır.)

Yeni bir process ll_start ile oluşturulur (Liste 30.4) . Bu fonksiyon yeni Lua durumu L1'i oluşturur ve verilen chunk'ı onda derler. Hata durumunda, hatayı orijinal durum L'e bildirir ve daha sonra ll_thread gövdesiyle yeni iş parçacığı  oluşturur(pthread_create) , gövdeye argüman olarak L1 yeni durumu geçilir. pthread_detach çağrısı sisteme bu iş parçacığından herhangi son cevap istemeyeceğimizi söyler.

Her yeni iş parçacığının gövdesi ll_thread fonksiyonudur (Liste 30.5). ll_start'dan karşılık gelen Lua durumunu alır, yığında yalnızca  önderli ana chunk ile. Yeni iş parçacığı standart Lua kütüphanelerini açar, lproc kütüphanesini açar ve ardından ana chunk'ını çağırır. Son olarak, koşul değişkenini (luaopen_lproc ile oluşturulan) yok eder ve Lua durumunu kapatır.

Modülden son fonksiyon, exit, oldukça basit:

static int ll_exit(lua_State *L){
  pthread_exit(NULL);
  return 0;
}

İşi bittiğinde akabinde tüm programın sonlanmasını önlemek için ana process'in bu fonksiyonu çağırması gerektiğini unutmayın yalnız.

Liste 30.5. yeni threadların gövdesi:

static void *ll_thread(void *arg){
  lua_State *L = (lua_State *)arg;
  luaL_openlibs(L); /* standard kütüphaneleri aç */
  lua_cpcall(L, luaopen_lproc, NULL); /* lproc kütüphanesini aç*/
  if (lua_pcall(L, 0, 0, 0) != 0) /* ana chunk'ı çağır */
    fprintf(stderr, "thread error: %s", lua_tostring(L, -1));
  pthread_cond_destroy(&getself(L)->cond);
  lua_close(L);
  return NULL;
}

Son adımımız, lproc modülü için açma fonksiyonunu tanımlamak. açma fonksiyonu luaopen_lproc (Liste 30.6) , modül fonksiyonlarını her zamanki gibi kaydettirmeli ama aynı zamanda çalışan process'in Proc yapısını oluşturmak ve ilklemek zorundadır.

Daha önce de söylediğim gibi, Lua'da process'lerin bu uygulaması en basit olanından. Yapabileceğiniz sayısız geliştirme var. Burada kısaca bazılarını ele alacağım.

İlk belirgin geliştirme,  eşleşme kanalı için doğrusal aramayı değiştirmek. Güzel bir alternatif, kanalı bulup her kanal için bağımsız bekleme listeleri kullanmak için bir hash tablosu(komut çizelgesi) kullanmak.

Diğer bir geliştirme, process yaratımının verimliliği ile ilgili. Yeni Lua statelerinin oluşturulması hafif bir işlem. Bununla birlikte tüm standart kütüphanelerin açılması, yeni bir durumu açmadan on kat daha fazla zaman alır. Çoğu process muhtemelen tüm standart kütüphanelere ihtiyaç duymaz; aslında çoğu yalnızca bir veya iki kütüphaneye ihtiyaç duyar. Bölüm 15.1'de ele aldığımız üzere kütüphanelerin ön-kayıtlanması kullanılarak kütüphanenin açma maliyetinden kaçınabiliriz. Bu yaklaşımla, her bir standart kütüphane için luaopen_* fonksiyonunu çağırmak yerine sadece bu fonksiyonu package.preload tablosuna koyarız . process, require"lib" 'i çağırırsa o zaman —ve ancak o zaman- require, kütüphaneyi açmak için ilişkilendirilmiş fonksiyonu çağırır. Aşağıdaki fonksiyonu bu kayıtlanmayı yapar:

static void registerlib(lua_State *L, const char *name,
                                      lua_CFunction f){
  lua_getglobal(L, "package");
  lua_getfield(L, -1, "preload"); /* 'package.preload' 'i getir*/
  lua_pushcfunction(L, f);
  lua_setfield(L, -2, name); /* package.preload[name] = f */
  lua_pop(L, 2); /*  ’package’ ve ’preload’ tablolarını pop et */
}

Temel kütüphaneyi açmak her zaman iyi bir fikirdir. Paket kütüphanesine de ihtiyacınız var; aksi takdirde diğer kütüphaneleri açmak için require kullanılabilir olmaz.

Liste 30.6. lproc modülü için açma fonksiyonu:

static const struct luaL_reg ll_funcs[] = {
    {"start", ll_start},
    {"send", ll_send},
    {"receive", ll_receive},
    {"exit", ll_exit},
    {NULL, NULL}
};

int luaopen_lproc(lua_State *L){
  /* kendi kontrol bloğunu oluştur */
  Proc *self = (Proc *)lua_newuserdata(L, sizeof(Proc));
  lua_setfield(L, LUA_REGISTRYINDEX, "_SELF");
  self->L = L;
  self->thread = pthread_self();
  self->channel = NULL;
  pthread_cond_init(&self->cond, NULL);
  luaL_register(L, "lproc", ll_funcs); /* kütüphaneyi aç */
  return 1;
}

Diğer tüm kütüphaneler opsiyonel olabilir. Bu nedenle luaL_openlibs'i çağırmak yerine yeni durumları açarken aşağıdaki openlibs fonksiyonunu çağırabiliriz:

static void openlibs(lua_State *L){
  lua_cpcall(L, luaopen_base, NULL); /* temel kütüphaneyi aç */
  lua_cpcall(L, luaopen_package, NULL); /* paket kütüphanesini aç */
  registerlib(L, "io", luaopen_io);
  registerlib(L, "os", luaopen_os);
  registerlib(L, "table", luaopen_table);
  registerlib(L, "string", luaopen_string);
  registerlib(L, "math", luaopen_math);
  registerlib(L, "debug", luaopen_debug);
}

Bir process bu kütüphanelerden birine ihtiyacı olduğunda, kütüphane require ile istenir ve require karşılık gelen luaopen_* fonksiyonunu çağırır.

Diğer iyileştirmeler iletişim temellerini kapsar. Örneğin, lproc.send ve lproc.receive'in bir eşleşme için ne kadar süre beklemesi gereğine ilişkin sınırlar sağlamak yararlı olur. özgün bir durum olarak, sıfır, bu fonksiyonları bloksuz hale getirilir. POSIX threadlarıyla, bu imkanı pthread_cond_timedwait kullanarak uygulayabiliriz.

Hiç yorum yok:

Yorum Gönder