Forum: D Programlama Dili RSS
Eş zamanlı programlama ile ilgili zorlu bir hata
acehreli (Moderatör) #1
Kullanıcı başlığı: Ali Çehreli
Üye Haz 2009 tarihinden beri · 4527 mesaj
Grup üyelikleri: Genel Moderatörler, Üyeler
Profili göster · Bu konuya bağlantı
Konu adı: Eş zamanlı programlama ile ilgili zorlu bir hata
DilimKombinasyonu konusuna devam ederken pis bir hatayla boğuşmak zorunda kaldım. :) Hatayı bulmam ve çözmem çok zamanımı aldı. :(

Aşağıdaki basitleştirilmiş program bu hatayı içeriyor. Program çok basit: main() bir işçi başlatıyor; ona bir Görev gönderiyor ve karşılığında da geriye bir int alıyor.

Ne yazık ki aşağıdaki program takılıp kalıyor (umarım platform farkları beni yalıltmıyordur :)):

import std.stdio;
import std.concurrency;
 
struct Görev
{
    immutable int[] dilim;
}
 
void foo(Tid sahip)
{
    receive(
        (Görev görev)
        {
            writeln("İşçi görevi aldı: ", görev);
            sahip.send(42);
        }
    );
}
 
void main()
{
    auto işçi = spawn(&foo, thisTid);
 
    immutable int[] dilim = [ 1, 2 ];
    işçi.send(Görev(dilim));
 
    receive(
        (int sonuç)
        {
            writeln("Sahip sonucu aldı: ", sonuç);
        }
    );
}

Eğer yapacak hiiiç başka işiniz yoksa bu noktada üzerinde çalışmak isteyebilirsiniz. Tabii program çok küçük olduğu için rastgele yerlerini değiştirmek bile ne ile ilgili olduğunu hemen göstermeye yetecektir. Ben çok daha uzun ve karmaşık bir programla boğuşmak zorundaydım. :)

Beni hatanın kaynağına götüren adımlar şunlardı (yararsız adımların çoğuna gerek görmüyorum ;)):

  • Çıkışa "İşçi görevi aldı" yazdırılmadığına göre acaba işçi bir hatayla mı sonlanıyordu? Bunu görmek için spawn() yerine spawnLinked() kullandım:

    auto işçi = spawnLinked(&foo, thisTid);

spawnLinked(), işçi sonlandığında sahip tarafta LinkTerminated hatası atılmasına neden olur. Güzel:

$ ./deneme
std.concurrency.LinkTerminated@std/concurrency.d(263): Link terminated


Dikkat ederseniz "İşçi görevi aldı" yine de yok.

  • İşçiyi bütünüyle bir try-catch bloğu arasına alarak hata ile mi sonuçlandığına bakmak istedim ama onunla hata yakalayamadım. Burada bir hata yapmışım: catch bloğunda Exception türünü kullanmışım.

(Notz: Meğerse hata bir AssertError imiş ve AssertError Exception'dan türemediği için catch(Exception) onu yakalayamamış (AssertError Error'dan türer.) Sonunda Exception ve Error'ın üst türleri olan Throwable türünü catch edince yakalayabildim. Ama bunu aşağıdaki adımlardan sonra farkettim.)

  • İşçiye Görev yerine int göndermeyi denedim; program doğru çalıştı. Acaba receive() Görev türündeki mesajı (Görev görev) türündeki işlev ile ilişkilendiremiyor muydu? Eğer öyleyse receive()'e bir (Variant mesaj) da ekledim. Böylece gönderdiğim Görev nasıl olsa ona takılırdı:

    receive(
        (Görev görev)
        {
            writeln("İşçi görevi aldı: ", görev);
            sahip.send(42);
        },
 
        (Variant mesaj)
        {
            writeln("Beklenmedik bir mesaj aldım: ", mesaj);
        }
    );

Ne yazık ki aynı hata! :(

  • Bunun üzerine daha önceki catch'teki hatamı farkettim ve Throwable yakalamayı denedim (Not: Normalde bunu yapmayın. Programın normal işleyişi sırasında Exception'dan daha üst türlerin yakalanması önerilmez çünkü yakalandığında programın durumu hakkında pek birşey söylenemez. Örneğin yapıların sonlandırıcı işlevleri işletilmiyor olabilir çünkü runtime bu tür bir hatanın onları çağıramayacak kadar ciddi olduğunu düşünür.)

void foo(Tid sahip)
{
    try {
 
        // ...
 
    } catch (Throwable hata) {
        writeln("İşçi hata ile çıkıyor: ", hata);
    }
}

Evet, öyle:

./deneme
İşçi hata ile çıkıyor: core.exception.AssertError@/usr/include/d/dmd/phobos/std/variant.d(286): Görev


Sonunda hatanın bir AssertError olduğunu ve variant.d'nin 286 numaralı satırında atıldığını görüyoruz. Tam yolu verilen kaynak kodu açıyorum:

                else
                {
                    // type is not assignable
                    if (src) assert(false, A.stringof)// <--- 286. satır
                }

Yani tür atanamaz bir türmüş. Nasıl? Hata mesajı o türün Görev olduğunu da söylüyor. Deneyelim:

    auto a = Görev();
    auto b = a;    // <-- Derleniyor 

Hani atanamazdı! Ah Ali! :) Yukarıdaki bir atama değil, kopyalayarak kurma. b, a'dan kopyalanıyor. Atama olabilmesi için b'nin önceden var olması gerekir. Deneyelim:

    auto a = Görev();
    auto b = Görev();
    b = a;    // <-- Derleme hatası 

Evet, atanamıyor:

deneme.d(86030): Error: variable deneme.main.b cannot modify struct with immutable members

Yani immutable üyeler değiştirilemezlermiş. Biliyoruuum... :) Üyeyi öyle seçerek hem kendimi sağlama almışım hem de bir mesaj olarak başka bir iş parçacığına gönderebilmişim. Çünkü asıl programa dönüp iki yerdeki immutable'ı kaldırınca şöyle oluyor:

struct Görev
{
    int[] dilim;    // <-- immutable değil
}
 
// ...
    int[] dilim = [ 1, 2 ];    // <-- immutable değil
    işçi.send(Görev(dilim));

Derleme hatası:

/usr/include/d/dmd/phobos/std/concurrency.d(319): Error: static assert  "Aliases to mutable thread-local data not allowed."
deneme.d(86031):        instantiated from here: send!(Görev)


Bunun önüne kilitlerle filan geçilebilir ama onlara girmek istemiyorum. Ben veriyi immutable olarak işaretlemek ve iş parçacıkları arasında serbestçe paylaşmak istiyorum. Değişmez olduğu için hiçbir tehlikesi olamaz.

Sonunda çözümü farkediyorum: Görevin üyesi değil, onun eriştirdiği veri immutable olmalı. Zaten bana yeterli olan da o:

struct Görev
{
    immutable(int)[] dilim;    // <-- Veri immutable ama üye değil
}
// ...
    immutable(int)[] dilim = [ 1, 2 ];
    işçi.send(Görev(dilim));

Sonunda istenen sonuç:

./deneme
İşçi görevi aldı: Görev([1, 2])
Sahip sonucu aldı: 42


Kusura bakmayın uzun oldu ama ben bu konudan çok ders aldım. :)

Bu arada, derleyicinin üye için 'immutable int[]' türünü kabul etmemisinin geçerli nedeni var. Görebiliyor musunuz? Benim onu görmem de biraz zaman aldı.

Ali
Avatar
Salih Dinçer #2
Üye Ock 2012 tarihinden beri · 1912 mesaj · Konum: İstanbul
Grup üyelikleri: Üyeler
Profili göster · Bu konuya bağlantı
Gerçekten çok pis bir hataymış, çok şey denedim, çözümü okumadım ve tabii ki beceremedim...:)

Şimdi bu iletiyi onayladıktan sonra iletinin geri kalanını okumalıyım... :rolleyes:
Bilgi paylaştıkça bir bakmışız; kar topu olmuş ve çığ gibi üzerimize geliyor...:)
Avatar
Salih Dinçer #3
Üye Ock 2012 tarihinden beri · 1912 mesaj · Konum: İstanbul
Grup üyelikleri: Üyeler
Profili göster · Bu konuya bağlantı
Yanıtlanan mesaj #1
Keyifle okudum, hele şu satırları...:)

acehreli:
Hani atanamazdı! Ah Ali! :) Yukarıdaki bir atama değil, kopyalayarak kurma. b, a'dan kopyalanıyor. Atama olabilmesi için b'nin önceden var olması gerekir...

Evet, atanamıyor:

deneme.d(86030): Error: variable deneme.main.b cannot modify struct with immutable members

Yani immutable üyeler değiştirilemezlermiş. Biliyoruuum... :)

Kusura bakmayın uzun oldu ama ben bu konudan çok ders aldım. :)
Özetle çözüme ulaştığına sevindim. Gerçi çözümü çok anlamasam da...:)

acehreli:
Bu arada, derleyicinin üye için 'immutable int[]' türünü kabul etmemisinin geçerli nedeni var. Görebiliyor musunuz? Benim onu görmem de biraz zaman aldı.
İşte anlamadığım için de göremiyorum. Yani immutable'ı parantezli kullanınca ne oluyor, kullanmayınca ne oluyor; gerçi ne olduğunu biliyoruz programımız çalışıyor. Ama işte aradaki farkı anlayamadım?
Bilgi paylaştıkça bir bakmışız; kar topu olmuş ve çığ gibi üzerimize geliyor...:)
Kadir Can #4
Üye Haz 2010 tarihinden beri · 413 mesaj
Grup üyelikleri: Üyeler
Profili göster · Bu konuya bağlantı
@Salih;
immutable int[] dediğimiz zaman dizimiz değişmez oluyor. Eğer böyle kullanırsak diziye sadece kurarken atama yapabiliriz, onun dışındaki atamalar hataya sebep olur.
Ama immutable (int)[] dersek sadece dizinin elemanları değişmez olur. Bu durumda diziye eleman ekleyebiliriz; ancak var olan elemanları değiştiremeyiz.
Örneğin;
immutable int[] sayılar;
sayılar ~= 10//Derleme hatası: Değişmez diziyi değiştirmeye çalışıyoruz 
Üstteki kullanımda dizi değişmezdir, ekleme bile yapamayız.
immutable (int)[] sayılar;
sayılar ~= 10// Yasal: Elemanları değişmez olan dizinin sonuna ekleme yapıyoruz 
Yukarıdaki kullanımda ise dizinin sadece var olan elemanları değişmez oluyor, ekleme yapabiliyoruz.
Özet olarak, birinci kullanımda immutable bir int[] tanımlarken, ikinci kullanımda immutable int'lerden oluşan bir dizi tanımlıyoruz.
erdem (Moderatör) #5
Üye Tem 2009 tarihinden beri · 981 mesaj · Konum: Eskişehir
Grup üyelikleri: Genel Moderatörler, Üyeler
Profili göster · Bu konuya bağlantı
Yanıtlanan mesaj #3
Ben de sorunun yanıtını kendi başıma bulamadım. Ama adımları denedim. Gerçekten hatayı iyi yakalamışsınız.

Kadir Can'ın mesajını okuduktan sonra ona katılıyorum :) İnce bir ayrıntı..
Avatar
Salih Dinçer #6
Üye Ock 2012 tarihinden beri · 1912 mesaj · Konum: İstanbul
Grup üyelikleri: Üyeler
Profili göster · Bu konuya bağlantı
Yanıtlanan mesaj #4
Kadir Can:
immutable (int)[] sayılar;
sayılar ~= 10// Yasal: Elemanları değişmez olan dizinin sonuna ekleme yapıyoruz 
Yukarıdaki kullanımda ise dizinin sadece var olan elemanları değişmez oluyor, ekleme yapabiliyoruz.
Tamam, şimdi anladım. Teşekkürler...

Eklenebilir değişmez dizi şu şekilde oluyor immutable(int)[],
Boyutu da değiştirilemeyen ise immutable(immutable(int)[])* ile dört bir yandan kuşatılıyor...:)

Dolayısıyla Ali hocanın en son sorduğu sorunun cevabı çok basit. Biz yapıyı kurarken üyesine bir şey ekleyemezsek olaylar karışır.

Aslında oldum olası, şu immutable'ı anlayabilmiş değilim. Şimdi bir ayrıntısını daha öğrenmiş olduk; belki de bir şeyi başında immutable yazdığımızda ne demek olduğunu! Çünkü kapsadığı alan çok geniş ve mümkünse parantezli kullanmalı.

(*) Bence bu çok saçma, kim eklenemeyen dinamik bir dizi ister ki!
Bilgi paylaştıkça bir bakmışız; kar topu olmuş ve çığ gibi üzerimize geliyor...:)
Bu mesaj Salih Dinçer tarafından değiştirildi; zaman: 2012-07-19, 13:08.
acehreli (Moderatör) #7
Kullanıcı başlığı: Ali Çehreli
Üye Haz 2009 tarihinden beri · 4527 mesaj
Grup üyelikleri: Genel Moderatörler, Üyeler
Profili göster · Bu konuya bağlantı
Salih Dinçer:
Boyutu da değiştirilemeyen ise immutable(immutable(int)[])* ile dört bir yandan kuşatılıyor...:)
...
(*) Bence bu çok saçma, kim eklenemeyen dinamik bir dizi ister ki!

O zaman buna şaşırabilirsin ama o türler aslında aynı çünkü D'de tür belirteçleri derinlemesinedir. Dilim immutable (veya const) olunca elemanlar da immutable (veya const) oluyor.

Yani türler aynı:

void main()
{
    immutable(immutable(int)[]) a;
    immutable int[] b;
    assert(typeid(a) == typeid(b));
}

Bu arada, tamam, bu işin düzeneğini anlıyoruz ama b a'dan atanabilse ne zararı olurdu? Ona uygun bir örnek düşünebiliyor musunuz? Yani dilin kuralları neden böyle? Zor bir soru değil tabii ama izin verilseydi zararı olabilecek bir örnek görsek? :)

Ali
Bu mesaj acehreli tarafından değiştirildi; zaman: 2012-07-19, 13:20.
Avatar
Salih Dinçer #8
Üye Ock 2012 tarihinden beri · 1912 mesaj · Konum: İstanbul
Grup üyelikleri: Üyeler
Profili göster · Bu konuya bağlantı
acehreli:
Yani türler aynı:

void main()
{
    immutable(immutable(int)[]) a;
    immutable int[] b;
    assert(typeid(a) == typeid(b));
}
Evet aynı, a'nın türünü typeid'den çalmıştım...:)

Sanırım sadece başına immutable koymak türü de türün yapısını da immutable yapmakta. Yani derleyicinin auto'su gibi tam otomatik bir şey. Ali hocanın sorularına cevap verecek örnek aklıma gelmiyor. Ama kendi sorduğum soruya cevap belki olabilir. Yani yukarıdaki koda göre b'ye ne zaman ihtiyaç duyarız?

Farz edelim yazılımınız ile ilgili bir takım kritik sabitleri, bir sabit dizide tutmak istiyoruz. Hatta bunların araya ekleme gibi yerlerinin kesinlikle değişmemesi gerekiyor. Öyle ki dizinin uzunluğunun da artması kurduğumuz foreach()'li yapıdan dolayı işleri karıştırıyor. Örneğin şöyle:
immutable float[] param = [ 0.9, 3.33, 7.2, 10.1 ] // Toplam 4 parametre 
Şimdi biz bunu sabit dizide yapmış olsaydık (float[4] param) ve yeni parametreler eklememiz gerekseydi her seferinde eşitiliğin her iki tarafını da düzenlememiz gerekecekti. Tabi enum gibi olanaklar var ama biz diziyle yapacağız ya...:)

Bu arada immutable bize bir olanağı daha sağlıyor. O da parametrelerin her birinin değişmemesini:
immutable float[] param = [ 0.9, 3.33, 7.2, 10.1 ] // Toplam 4 parametre
param[0] = 0// Derleme hatası... 
D'yi seviyorum...:)
Bilgi paylaştıkça bir bakmışız; kar topu olmuş ve çığ gibi üzerimize geliyor...:)
acehreli (Moderatör) #9
Kullanıcı başlığı: Ali Çehreli
Üye Haz 2009 tarihinden beri · 4527 mesaj
Grup üyelikleri: Genel Moderatörler, Üyeler
Profili göster · Bu konuya bağlantı
Aslında bu örneğin konuştuklarımızdan pek bir farkı yok. r0, r1, ve r2 referansları S.dilim'in immutable olmasına güveniyorlar. Eğer b'ye atamaya izin verilseydi durum onların sandıklarından farklı olurdu:

struct S
{
    immutable int[] dilim;
}
 
void main()
{
    auto b = S();
 
    foo(b);    // <-- dolaylı olarak r0'ı ayarlıyor
    auto r1 = &b.dilim;
    auto r2 = b.dilim.length;
 
    auto a = S();
    // Buna izin verilseydi r0, r1, ve r2 aldanmış olurlardı.
    // b = a;
}
 
immutable(int[]) * r0;
 
void foo(ref S s)
{
    r0 = &s.dilim;
}

Ali
Doğrulama Kodu: VeriCode Lütfen resimde gördüğünüz doğrulama kodunu girin:
İfadeler: :-) ;-) :-D :-p :blush: :cool: :rolleyes: :huh: :-/ <_< :-( :'( :#: :scared: 8-( :nuts: :-O
Özel Karakterler:
Bağlı değilsiniz. · Şifremi unuttum · ÜYELİK
This board is powered by the Unclassified NewsBoard software, 20100516-dev, © 2003-10 by Yves Goergen
Şu an: 2017-11-22, 02:59:48 (UTC -08:00)