Güvenli kapakların uygulanması için çöp toplama gerekli mi?


14

Kısa süre önce, diğer kavramların yanı sıra kapakların sunulduğu programlama dilleri üzerine çevrimiçi bir kursa katıldım. Sorumu sormadan önce bu bağlamdan ilham almak için iki örnek yazıyorum.

İlk örnek, 1'den x'e kadar olan sayıların listesini üreten bir SML işlevidir; burada x, işlevin parametresidir:

fun countup_from1 (x: int) =
    let
        fun count (from: int) =
            if from = x
            then from :: []
            else from :: count (from + 1)
    in
        count 1
    end

SML REPL'de:

val countup_from1 = fn : int -> int list
- countup_from1 5;
val it = [1,2,3,4,5] : int list

countup_from1Fonksiyon yardımcı kapatılması kullandığı counto yakalar ve kullanımları değişkeni xonun bağlamdan.

İkinci örnekte, bir işlevi çağırdığımda create_multiplier t, argümanını t ile çarpan bir işlevi (aslında bir kapatma) geri alıyorum:

fun create_multiplier t = fn x => x * t

SML REPL'de:

- fun create_multiplier t = fn x => x * t;
val create_multiplier = fn : int -> int -> int
- val m = create_multiplier 10;
val m = fn : int -> int
- m 4;
val it = 40 : int
- m 2;
val it = 20 : int

Değişken m, işlev çağrısı tarafından döndürülen kapatmaya bağlıdır ve şimdi bunu isteğe bağlı olarak kullanabilirim.

Şimdi, kapağın kullanım ömrü boyunca düzgün çalışması için, yakalanan değişkenin ömrünü uzatmamız gerekir t(örnekte bir tamsayıdır, ancak her türden bir değer olabilir). Bildiğim kadarıyla, SML'de bu çöp toplama ile mümkün olur: Kapak, daha sonra kapak imha edildiğinde çöp toplayıcı tarafından atılan yakalanan değere bir referans tutar.

Benim sorum: Genel olarak, çöp toplama, kapakların güvenli olmasını sağlamak için mümkün olan tek mekanizma mıdır (tüm kullanım ömrü boyunca çağrılabilir)?

Veya çöp toplama olmadan kapakların geçerliliğini sağlayabilecek diğer mekanizmalar nelerdir: Yakalanan değerleri kopyalayın ve kapağın içinde saklayın? Kapanmanın ömrünü, yakalanan değişkenlerinin süresi dolduktan sonra çağrılamayacak şekilde kısıtlansın mı?

En popüler yaklaşımlar nelerdir?

DÜZENLE

Yukarıdaki örneğin yakalanan değişken (ler) i kapatılarak kopyalanarak açıklanabileceğini / uygulanabileceğini sanmıyorum. Genel olarak, yakalanan değişkenler herhangi bir tipte olabilir, örneğin çok büyük (değişmez) bir listeye bağlanabilirler. Dolayısıyla, uygulamada bu değerleri kopyalamak çok verimsiz olacaktır.

Tamlık uğruna, referansları (ve yan etkileri) kullanan başka bir örnek:

(* Returns a closure containing a counter that is initialized
   to 0 and is incremented by 1 each time the closure is invoked. *)
fun create_counter () =
    let
        (* Create a reference to an integer: allocate the integer
           and let the variable c point to it. *)
        val c = ref 0
    in
        fn () => (c := !c + 1; !c)
    end

(* Create a closure that contains c and increments the value
   referenced by it it each time it is called. *)
val m = create_counter ();

SML REPL'de:

val create_counter = fn : unit -> unit -> int
val m = fn : unit -> int
- m ();
val it = 1 : int
- m ();
val it = 2 : int
- m ();
val it = 3 : int

Bu nedenle, değişkenler referans olarak da yakalanabilir ve onları ( create_counter ()) oluşturan işlev çağrısı tamamlandıktan sonra da canlı kalır.


2
Kapatılan değişkenler çöp toplamadan korunmalı ve kapatılmayan değişkenler çöp toplama için uygun olmalıdır. Bir değişkenin kapatılıp kapatılmadığını güvenilir bir şekilde izleyebilen herhangi bir mekanizma, değişkenin kapladığı belleği de güvenilir bir şekilde geri kazanabilir.
Robert Harvey

3
@btilly: Yeniden sayım, bir çöp toplayıcı için birçok farklı uygulama stratejisinden sadece biridir. GC'nin bu soru için nasıl uygulandığı gerçekten önemli değil.
Jörg W Mittag

3
@btilly: "Gerçek" çöp toplama ne anlama geliyor? Yeniden sayım GC'yi uygulamanın başka bir yoludur. Muhtemelen yeniden sayma ile döngüleri toplama zorlukları nedeniyle izleme daha popülerdir. (Genellikle, yine de ayrı bir izleme GC ile sonuçlanırsınız, bu yüzden neden bir tane ile geçebilirseniz iki GC uygulamak için uğraşın.) Ancak döngülerle başa çıkmanın başka yolları da var. 1) Sadece yasakla. 2) Sadece görmezden gelin. (Hızlı bir kerelik komut dosyaları için bir uygulama yapıyorsanız, neden olmasın?) 3) Bunları açıkça algılamaya çalışın. (Anlaşmanın mevcut olması bunu hızlandırabilir.)
Jörg W Mittag

1
İlk etapta neden kapama istediğinize bağlıdır. Diyelim ki tam bir lambda hesabı anlambilimi uygulamak istiyorsanız, kesinlikle GC'ye ihtiyacınız var , nokta. Başka yolu yok. Eğer kapanışlara çok benzeyen bir şey istiyorsanız, ancak bunun tam anlamını takip etmeyen (C ++, Delphi gibi) her şeyi yapın - ne isterseniz yapın, bölge analizini kullanın, tamamen manuel bellek yönetimini kullanın.
SK-logic

2
@Mason Wheeler: Kapaklar sadece değerlerdir, genel olarak çalışma zamanında nasıl hareket ettirileceğini tahmin etmek mümkün değildir. Bu anlamda, özel bir şey değildir, aynı şey bir dize, bir liste vb. İçin de geçerlidir.
Giorgio

Yanıtlar:


14

Rust programlama dili bu açıdan ilginçtir.

Rust, isteğe bağlı bir GC'ye sahip bir sistem dilidir ve en başından itibaren kapaklarla tasarlanmıştır .

Diğer değişkenler gibi, pas kapakları çeşitli aromalara sahiptir. Yığın kapakları , en yaygın olanları, tek seferlik kullanım içindir. Yığında yaşıyorlar ve her şeye referans verebilirler. Sahip olunan kapaklar yakalanan değişkenlerin sahipliğini alır. Bence küresel bir yığın olan "değişim yığını" üzerinde yaşıyorlar. Kullanım ömürleri, kimin sahip olduğuna bağlıdır. Yönetilen kapaklar , görev yerel yığını üzerinde yaşar ve görevin GC'si tarafından izlenir. Yine de yakalama sınırlamalarından emin değilim.


1
Rust diline çok ilginç bir bağlantı ve referans. Teşekkürler. +1.
Giorgio

1
Bir cevabı kabul etmeden önce çok düşündüm çünkü Mason'un cevabını da çok bilgilendirici buluyorum. Bunu seçtim çünkü hem bilgilendirici hem de daha az bilinen bir dili, kapanışlara orijinal bir yaklaşımla gösteriyor.
Giorgio

Bunun için teşekkürler. Bu genç dil konusunda çok hevesliyim ve ilgimi paylaşmaktan mutluluk duyuyorum. Rust'u duymadan önce GC olmadan güvenli kapanışların mümkün olup olmadığını bilmiyordum.
Ocak'ta bar

9

Ne yazık ki bir GC ile başlamak sizi XY sendromunun kurbanı yapar:

  • kapanışlar, kapama yaptıkları değişkenlerden daha fazla kapanma gerektirdiği sürece yaşamayı gerektirir (güvenlik nedeniyle)
  • GC kullanarak bu değişkenlerin ömrünü yeterince uzatabiliriz
  • XY sendromu: ömrünü uzatmak için başka mekanizmalar var mı?

Bununla birlikte, bir değişkenin ömrünü uzatma fikrinden ziyade bir kapatma için gerekli değildir ; sadece GC tarafından getirildi; asıl güvenlik ifadesi sadece kapalı değişkenlerin kapanma kadar uzun süre yaşayacağıdır (ve bu bile titrek olsa da, kapatmanın son çağrılmasından sonraya kadar yaşaması gerektiğini söyleyebiliriz).

Aslında görebildiğim iki yaklaşım var (ve potansiyel olarak birleştirilebilirler):

  1. Kapalı değişkenlerin ömrünü uzatın (örneğin bir GC'nin yaptığı gibi)
  2. Kapak ömrünü kısıtla

İkincisi sadece simetrik bir yaklaşımdır. Sık kullanılmaz, ancak Rust gibi, bölgeye duyarlı bir sisteminiz varsa, kesinlikle mümkündür.


7

Değişkenleri değere göre yakalarken güvenli kapanmalar için çöp toplama gerekmez. Öne çıkan örneklerden biri C ++. C ++ standart çöp toplama özelliğine sahip değildir. C ++ 11'deki Lambda'lar kapanır (çevredeki kapsamdan yerel değişkenleri yakalarlar). Bir lambda tarafından yakalanan her değişkenin değere veya referansa göre yakalanması belirtilebilir. Referans olarak yakalanırsa, güvenli olmadığını söyleyebilirsiniz. Ancak, bir değişken değere göre yakalanırsa, güvenlidir, çünkü yakalanan kopya ve orijinal değişken ayrıdır ve bağımsız ömürleri vardır.

Verdiğiniz SML örneğinde açıklamak kolaydır: değişkenler değere göre yakalanır. Herhangi bir değişkenin "ömrünü uzatmaya" gerek yoktur, çünkü değerini sadece kapağa kopyalayabilirsiniz. Bu mümkündür, çünkü ML'de değişkenler atanamaz. Dolayısıyla, bir kopya ile birçok bağımsız kopya arasında fark yoktur. SML'nin çöp toplama özelliği olmasına rağmen, değişkenlerin kapanışlarla yakalanmasıyla ilgili değildir.

Değişkenleri referansla (tür) yakalarken güvenli kapanmalar için de çöp toplamaya gerek yoktur. Bunun bir örneği, Apple Blocks'un C, C ++, Objective-C ve Objective-C ++ dillerine uzantısıdır. C ve C ++ 'da standart çöp toplama yoktur. Değişkenleri varsayılan olarak değere göre engeller. Bununla birlikte, bir yerel değişken ile bildirilirse __block, bloklar onları "referans olarak" görünür bir şekilde yakalar ve güvenlidirler - blok tanımlandığı kapsamdan sonra bile kullanılabilirler. Burada olan şey, __blockdeğişkenlerin aslında bir altındaki özel yapı ve bloklar kopyalandığında (bloklar ilk etapta kapsam dışında kullanmak için kopyalanmalıdır),__block değişken yığın içine ve blok bellek yönetir, ben referans sayma inanıyorum.


4
"Kapaklar için çöp toplama gerekli değildir.": Soru, dilin güvenli kapanmaları zorlayabilmesi için gerekli olup olmadığıdır. C ++ 'da güvenli kapanışlar yazabileceğimi biliyorum ama dil onları zorlamıyor. Yakalanan değişkenlerin ömrünü uzatan kapaklar için sorumun düzenlenmesine bakın.
Giorgio

1
Sanırım soru tekrar ele alınabilir: güvenli kapanışlar için .
Matthieu M.Mar

1
Başlık "güvenli kapanışlar" terimini içeriyor, sizce daha iyi bir şekilde formüle edebileceğimi düşünüyor musunuz?
Giorgio

1
İkinci paragrafı düzeltebilir misiniz? SML'de, kapaklar yakalanan değişkenler tarafından başvurulan verilerin ömrünü uzatır. Ayrıca, değişken atayamayacağınız (bağlayıcılığını değiştiremeyeceğiniz) ancak değişken verileriniz (içinden ref's) olduğu doğrudur . Dolayısıyla, tamam, kapakların uygulanmasının çöp toplama ile ilgili olup olmadığı tartışılabilir, ancak yukarıdaki ifadeler düzeltilmelidir.
Giorgio

1
@Giorgio: Peki ya şimdi? Ayrıca, kapanışların ele geçirilen bir değişkenin ömrünü uzatmaya gerek duymadığına dair hangi ifadeyi yanlış buluyorsunuz? Değişken verilerden bahsederken ref, bir yapıya işaret eden referans türlerinden ( s, diziler vb.) Bahsediyorsunuz. Ama değer referansın kendisi, işaret ettiği şey değil. var a = ref 1Bir kopyanız varsa ve bir kopyasını çıkarırsanız var b = ave kullanırsanız b, bu hala kullandığınız anlamına mı gelir a? Aynı yapıya işaret ediyor amusunuz? Evet. Bu tür
SML'de

6

Kapakları uygulamak için çöp toplama gerekli değildir. 2008 yılında, çöp toplanmayan Delphi dili, bir kapanış uygulaması ekledi. Şöyle çalışır:

Derleyici, kaputun altında bir kapağı temsil eden bir Arabirim uygulayan bir işlev nesnesi oluşturur. Tüm kapalı yerel değişkenler, kapalı yordam için yerel ayarlardan functor nesnesindeki alanlara değiştirilir. Bu, durumun işlevci olduğu sürece korunmasını sağlar.

Bu sistem için sınırlama, kapsamı kapalı fonksiyonun sınırlaması olan yerel ayarlar olmadıkları için, kapalı fonksiyona atıfta bulunarak iletilen herhangi bir parametrenin ve fonksiyonun sonuç değerinin functor tarafından yakalanamamasıdır.

Functor, geliştiriciye bir Arabirim yerine bir işlev işaretçisi gibi görünmesini sağlamak için sözdizimsel şeker kullanılarak kapatma referansı ile belirtilir. Functor nesnesinin (ve sahip olduğu tüm durumun) gerektiği kadar "canlı" kalmasını sağlamak için Delphi'nin arabirimler için referans sayma sistemini kullanır ve ardından yeniden sayım 0'a düştüğünde serbest kalır.


1
Ah, sadece argümanları değil, sadece yerel değişkeni yakalamak mümkün! Bu makul ve akıllı bir takas gibi görünüyor! +1
Giorgio

1
@Giorgio: Var parametreleri olan argümanları yakalayamaz .
Mason Wheeler

2
Paylaşılan özel durum üzerinden iletişim kuran 2 kapatma hakkına da sahip olursunuz. Temel kullanım durumlarında bununla karşılaşmazsınız, ancak karmaşık şeyler yapma yeteneğinizi sınırlar. Hala mümkün olanın harika bir örneği!
btilly

3
@btilly: Aslında, aynı kapatma işlevinin içine 2 kapak koyarsanız, bu tamamen yasaldır. Aynı functor nesnesini paylaşırlar ve birbirleriyle aynı durumu değiştirirlerse, birindeki değişiklikler diğerine yansıtılır.
Mason Wheeler

2
@MasonWheeler: "Hayır. Çöp toplama, doğası gereği belirleyici değildir; herhangi bir nesnenin, ne zaman gerçekleşirse toplanacağının garantisi yoktur. Ancak referans sayımı belirleyicidir: derleyici tarafından nesnenin sayısı 0'a düştükten hemen sonra serbest bırakılacaktır. " Her seferinde bir kuruşum olsaydı, mitin sürdüğünü duydum. OCaml, deterministik bir GC'ye sahiptir. C ++ iş parçacığı kasası shared_ptrdeterministik değildir çünkü yıkıcılar sıfıra inmek için yarışır.
Jon Harrop
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.