Semafor, çok iş parçacıklı sorunları çözmek için sıklıkla kullanılan bir programlama konseptidir. Topluma sorum:
Semafor nedir ve nasıl kullanılır?
Semafor, çok iş parçacıklı sorunları çözmek için sıklıkla kullanılan bir programlama konseptidir. Topluma sorum:
Semafor nedir ve nasıl kullanılır?
Yanıtlar:
Semaforları bir gece kulübünde fedailer olarak düşünün. Kulüpte bir kerede izin verilen özel sayıda insan var. Kulüp doluysa kimsenin girmesine izin verilmez, ancak bir kişi ayrılır ayrılmaz başka bir kişi girebilir.
Bu, belirli bir kaynak için tüketici sayısını sınırlamanın bir yoludur. Örneğin, bir uygulamadaki bir veritabanına eşzamanlı çağrı sayısını sınırlamak için.
İşte C # :-) çok pedagojik bir örnek
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
namespace TheNightclub
{
public class Program
{
public static Semaphore Bouncer { get; set; }
public static void Main(string[] args)
{
// Create the semaphore with 3 slots, where 3 are available.
Bouncer = new Semaphore(3, 3);
// Open the nightclub.
OpenNightclub();
}
public static void OpenNightclub()
{
for (int i = 1; i <= 50; i++)
{
// Let each guest enter on an own thread.
Thread thread = new Thread(new ParameterizedThreadStart(Guest));
thread.Start(i);
}
}
public static void Guest(object args)
{
// Wait to enter the nightclub (a semaphore to be released).
Console.WriteLine("Guest {0} is waiting to entering nightclub.", args);
Bouncer.WaitOne();
// Do some dancing.
Console.WriteLine("Guest {0} is doing some dancing.", args);
Thread.Sleep(500);
// Let one guest out (release one semaphore).
Console.WriteLine("Guest {0} is leaving the nightclub.", args);
Bouncer.Release(1);
}
}
}
Michael Barr tarafından Demonsize Edilen Muteksler ve Semaforlar makalesi , muteksleri ve semaforları farklı kılan, ne zaman kullanılması ve kullanılmaması gerektiği hakkında kısa bir giriş niteliğindedir. Burada birkaç temel paragraftan alıntı yaptım.
Kilit nokta mutekslerin paylaşılan kaynakları korumak için, semaforlar ise sinyalizasyon için kullanılması gerektiğidir. Paylaşılan kaynakları korumak için genellikle semaforları veya sinyal için muteksleri kullanmamalısınız. Örneğin, paylaşılan kaynakları korumak için semaforları kullanma konusunda fedai benzetmesi ile ilgili sorunlar vardır - bunları bu şekilde kullanabilirsiniz, ancak hataları teşhis etmek zor olabilir.
Muteksler ve semaforlar uygulamalarında bazı benzerliklere sahip olsalar da, her zaman farklı kullanılmalıdırlar.
En sık sorulan soruya verilen en yaygın (ama yine de yanlış) cevap mutekslerin ve semaforların birbirine çok benzemesidir, tek önemli fark semaforların birden fazla sayılabilmesidir. Neredeyse tüm mühendisler, muteksin kodun kritik bölümleri içinde karşılıklı dışlama sağlayarak paylaşılan bir kaynağı korumak için kullanılan bir ikili bayrak olduğunu doğru bir şekilde anlamış görünüyor. Ancak, "sayma semaforunun" nasıl kullanılacağına dair genişleme istendiğinde, çoğu mühendis - yalnızca güven derecelerine göre değişiyor - ders kitabının bazı eşdeğer kaynakları korumak için kullanıldığını düşünüyor.
...
Bu noktada, ortak kaynakları korumak için banyo anahtarları fikri - banyo - kullanılarak ilginç bir benzetme yapılır. Bir dükkanda tek bir banyo varsa, o kaynağı korumak ve birden fazla kişinin aynı anda kullanmasını önlemek için tek bir anahtar yeterli olacaktır.
Birden fazla banyo varsa, bu anahtarlar aynı şekilde anahtarlanabilir ve birden fazla anahtar yapılabilir - bu, semaforun yanlış kullanılmasına benzer. Bir anahtara sahip olduğunuzda, aslında hangi tuvaletin mevcut olduğunu bilmiyorsunuz ve bu yoldan giderseniz, muhtemelen bu bilgileri sağlamak ve zaten meşgul olan bir banyoyu almadığınızdan emin olmak için muteksleri kullanacaksınız. .
Bir semafor, esasen aynı kaynağın birçoğunu korumak için yanlış araçtır, ancak bu, kaç kişinin bunu düşündüğü ve kullandığıdır. Bouncer benzetmesi belirgin şekilde farklıdır - aynı türden kaynaklardan birkaç tane yoktur, bunun yerine birden fazla eşzamanlı kullanıcıyı kabul edebilen bir kaynak vardır. Sanırım bir semafor bu gibi durumlarda kullanılabilir, ancak nadiren benzetmenin gerçekte tutulduğu gerçek dünya durumları vardır - daha sık aynı türden birkaç tane vardır, ancak yine de banyolar gibi kullanılamaz. bu yoldan.
...
Bir semaforun doğru kullanımı bir görevden diğerine sinyal vermek içindir. Muteks, koruduğu paylaşılan kaynağı kullanan her görev tarafından, her zaman bu sırada alınmalı ve serbest bırakılmalıdır. Bunun aksine, semafor kullanan görevler ikisini birden değil sinyal veya beklemeyi kullanır. Örneğin, Görev 1, "güç" düğmesine basıldığında belirli bir semaforu yayınlamak için kod içerebilir (yani sinyal veya artış) ve ekranı uyandıran Görev 2 aynı semafora bağlıdır. Bu senaryoda, görevlerden biri olay sinyalinin üreticisidir; diğeri tüketici.
...
Burada, mutekslerin gerçek zamanlı işletim sistemlerine kötü bir şekilde müdahale ettiği ve kaynak paylaşımı nedeniyle daha önemli bir görevden önce daha az önemli bir görevin yerine getirilebileceği öncelikli ters çevirmeye neden olduğu önemli bir noktaya dikkat çekilmektedir. Kısacası, daha düşük öncelikli bir görev, bir kaynağı kapmak için bir muteks kullandığında, A, sonra B'yi yakalamaya çalışır, ancak B kullanılamadığı için duraklatılır. Beklerken, daha yüksek öncelikli bir görev gelir ve A'ya ihtiyaç duyar, ancak zaten bağlı ve B'yi beklediği için bile çalışmayan bir işlemle bunu çözmenin birçok yolu var, ancak çoğu zaman düzeltildi muteksi ve görev yöneticisini değiştirerek. Muteks bu durumlarda ikili bir semafordan çok daha karmaşıktır,
...
Muteksler ve semaforlar arasındaki yaygın modern karışıklığın nedeni tarihseldir, çünkü Djikstra tarafından Semafor'un (bu makalede "S") 1974 icadına kadar uzanmaktadır. Bu tarihten önce, bilgisayar bilimcileri tarafından bilinen kesinti güvenliği görev senkronizasyonu ve sinyalizasyon mekanizmalarının hiçbiri ikiden fazla görev tarafından kullanılmak üzere etkili bir şekilde ölçeklendirilemedi. Dijkstra'nın devrim niteliğinde, güvenli ve ölçeklenebilir Semaforu, hem kritik bölüm koruması hem de sinyalizasyonda uygulandı. Ve böylece karışıklık başladı.
Bununla birlikte, daha sonra, öncelik tabanlı önleyici RTOS'un (örneğin, VRTX, ca. 1980), RMA'yı oluşturan akademik makalelerin yayınlanması ve öncelikli ters çevirme nedeniyle ortaya çıkan sorunlar ve öncelikli bir makalenin ortaya çıkmasından sonra işletim sistemi geliştiricileri için açık hale geldi. 1990'da kalıtım protokolleri, 3 mutekslerin ikili sayaçlı semaforlardan daha fazlası olması gerektiği ortaya çıktı.
Mutex: kaynak paylaşımı
Semafor: sinyal verme
Yan etkileri dikkatle değerlendirmeden birini diğeri için kullanmayın.
Mutex: bir kaynağa münhasır üye erişimi
Semafor: bir kaynağa n-üye erişimi
Yani bir muteks bir sayaca, dosyaya, veritabanına vb. Erişimi senkronize etmek için kullanılabilir.
Bir semafor aynı şeyi yapabilir ancak sabit sayıda eşzamanlı arayanı destekler. Örneğin, çok iş parçacıklı uygulamam en çok 3 eşzamanlı bağlantıyla veritabanına vuracak şekilde veritabanı çağrılarımı bir semaforda (3) sarabilirim. Üç yuvadan biri açılana kadar tüm denemeler engellenir. Saf daraltma yapmak gibi şeyleri gerçekten çok kolay hale getiriyorlar.
Şoför de dahil olmak üzere toplam 3 ( arka ) +2 ( ön ) kişiyi barındırabilecek bir taksi düşünün . Yani, bir semaphore
arabada aynı anda sadece 5 kişiye izin verir. Ve bir mutex
arabanın tek bir koltuğunda sadece 1 kişiye izin verir.
Bu nedenle, Mutex
bir kaynak için ( OS iş parçacığı gibi ) özel erişime Semaphore
izin vermek , a aynı anda n sayıda kaynağa erişime izin vermektir .
@Craig:
Semafor bir kaynağı kilitlemenin bir yoludur, böylece bir kod parçası yürütülürken, yalnızca bu kod parçasının o kaynağa erişimi olur. Bu, iki iş parçacığının aynı anda bir kaynağa erişmesini engeller ve bu da sorunlara neden olabilir.
Bu yalnızca bir iş parçacığıyla sınırlı değildir. Bir semafor, sabit sayıda iş parçacığının bir kaynağa erişmesine izin verecek şekilde yapılandırılabilir.
Semafor da ... semafor olarak kullanılabilir. Örneğin, bir kuyruğa veri sıkma işleminiz varsa ve kuyruktan veri alan tek bir görev varsa. Tüketim görevinizin mevcut veriler için kuyruğu sürekli olarak yoklamasını istemiyorsanız, semafor kullanabilirsiniz.
Burada semafor bir dışlama mekanizması olarak değil, bir sinyalleme mekanizması olarak kullanılır. Tüketici görev semaforda bekliyor Üretim görevi semaforda gönderiyor.
Bu şekilde, tüketen görev sadece ve sadece ayıklanacak veriler olduğunda çalışır
Eşzamanlı programlar oluşturmak için iki temel kavram vardır - senkronizasyon ve karşılıklı dışlama. Bu iki kilit türünün (semaforlar daha genel olarak bir tür kilitleme mekanizmasıdır) senkronizasyon ve karşılıklı dışlama elde etmemize nasıl yardımcı olduğunu göreceğiz.
Semafor, hem senkronizasyon hem de karşılıklı dışlama uygulayarak eşzamanlılık elde etmemize yardımcı olan bir programlama yapısıdır. Semaforlar İkili ve Sayma olmak üzere iki türdür.
Bir semaforun iki bölümü vardır: sayaç ve belirli bir kaynağa erişmek için bekleyen görevlerin listesi. Bir semafor iki işlem gerçekleştirir: bekle (P) [bu bir kilit almak gibidir] ve (V) [bir kilidin serbest bırakılmasına benzer] serbest bırakılır - bunlar semaforda gerçekleştirilebilecek tek iki işlemdir. İkili bir semaforda, sayaç mantıksal olarak 0 ile 1 arasındadır. Bunun iki değerli bir kilide benzer olduğunu düşünebilirsiniz: açık / kapalı. Sayma semaforunun sayı için birden fazla değeri vardır.
Anlamak önemli olan semafor sayacının engellemesi gerekmeyen görevlerin sayısını izlemesi, yani ilerleme kaydedebilmesidir. Görevler engellenir ve yalnızca sayaç sıfır olduğunda kendilerini semaforun listesine ekler. Bu nedenle, bir görev ilerleyemiyorsa P () yordamındaki listeye eklenir ve V () yordamı kullanılarak "serbest bırakılır".
Şimdi, ikili semaforların senkronizasyonu ve karşılıklı dışlamayı çözmek için nasıl kullanılabileceğini görmek oldukça açıktır - bunlar esasen kilitlerdir.
ex. Senkronizasyon:
thread A{
semaphore &s; //locks/semaphores are passed by reference! think about why this is so.
A(semaphore &s): s(s){} //constructor
foo(){
...
s.P();
;// some block of code B2
...
}
//thread B{
semaphore &s;
B(semaphore &s): s(s){} //constructor
foo(){
...
...
// some block of code B1
s.V();
..
}
main(){
semaphore s(0); // we start the semaphore at 0 (closed)
A a(s);
B b(s);
}
Yukarıdaki örnekte B2, yalnızca B1 yürütmeyi tamamladıktan sonra yürütülebilir. Diyelim ki A iş parçacığı önce yürütülür - sem.P () 'ye geçer ve sayaç 0 (kapalı) olduğundan bekler. B dişi gelir, B1'i bitirir ve sonra A ipini serbest bırakır - bu da B2'yi tamamlar. Böylece senkronizasyon elde ederiz.
Şimdi ikili semafor ile karşılıklı dışlamaya bakalım:
thread mutual_ex{
semaphore &s;
mutual_ex(semaphore &s): s(s){} //constructor
foo(){
...
s.P();
//critical section
s.V();
...
...
s.P();
//critical section
s.V();
...
}
main(){
semaphore s(1);
mutual_ex m1(s);
mutual_ex m2(s);
}
Karşılıklı dışlama da oldukça basittir - m1 ve m2 aynı anda kritik bölüme giremez. Bu nedenle, her bir iş parçacığı iki kritik bölümü için karşılıklı dışlama sağlamak üzere aynı semaforu kullanır. Şimdi, daha fazla eşzamanlılığa sahip olmak mümkün mü? Kritik bölümlere bağlıdır. (Karşılıklı dışlama elde etmek için semaforları nasıl kullanabileceğinizi düşünün .. ipucu: sadece bir semafor kullanmam gerekir mi?)
Semafor sayma: Birden fazla değere sahip bir semafor. Bunun ne anlama geldiğine bakalım - birden fazla değere sahip bir kilit ?? Çok açık, kapalı ve ... hmm. Karşılıklı dışlama veya senkronizasyonda çok aşamalı kilit ne işe yarar?
İkisinden daha kolay olalım:
Sayma semaforu kullanarak senkronizasyon: Diyelim ki 3 göreviniz var - 3'ten sonra yürütülmesini istediğiniz # 1 ve 2. Senkronizasyonunuzu nasıl tasarlarsınız?
thread t1{
...
s.P();
//block of code B1
thread t2{
...
s.P();
//block of code B2
thread t3{
...
//block of code B3
s.V();
s.V();
}
Yani semaforunuz kapalı başlarsa, t1 ve t2 bloğunun semafor listesine eklenmesini sağlarsınız. Sonra tüm önemli t3 gelir, işini bitirir ve t1 ve t2'yi serbest bırakır. Hangi sırayla serbest bırakılırlar? Semafor listesinin uygulanmasına bağlıdır. FIFO olabilir, belirli bir önceliğe dayalı olabilir, vb. (Not: t1 ve t2'nin belirli bir sırada yürütülmesini istiyorsanız ve semaforun uygulanmasından haberdar değilseniz, P'lerinizi ve V'lerinizi nasıl düzenleyeceğinizi düşünün)
(Öğrenin: V sayısı P sayısından fazla olursa ne olur?)
Karşılıklı Dışlama Sayma semaforlarını kullanma: Bunun için kendi sahte kodunuzu oluşturmanızı istiyorum (işleri daha iyi anlamanıza yardımcı olur!) - ancak temel kavram şudur: sayaç sayma semaforu = N, N görevlerinin kritik bölüme serbestçe girmesine izin verir . Bunun anlamı, kritik bölüme N göreviniz (veya isterseniz, iş parçacıkları) girmenizdir, ancak N + 1. görev engellenir (favori engellenen görev listemize gider) ve yalnızca V semaforu olduğunda izin verilir en azından bir kere. Yani semafor sayacı, 0 ve 1 arasında sallanmak yerine, şimdi 0 ve N arasında gidip N görevlerin serbestçe girip çıkmasına izin vererek kimseyi engellemiyor!
Tanrım, neden bu kadar aptalca bir şeye ihtiyacın olsun ki? Birden fazla erkeğin bir kaynağa erişmesine izin vermemek, karşılıklı dışlanmanın bütün amacı değil midir ?? (İpucu İpucu ... Bilgisayarınızda her zaman yalnızca bir sürücü yoktur, değil mi??)
Düşünmek için : Karşılıklı dışlama tek başına sayma semaforu elde etmekle mi elde edilir? Bir kaynağın 10 örneğine sahipseniz ve 10 iş parçacığı gelirse (sayma semaforundan) ve ilk örneği kullanmaya çalışırsanız ne olur?
Semafor, üzerinde iki değiştirme işleminin tanımlandığı doğal bir sayı (yani sıfıra eşit veya sıfıra eşit bir tam sayı) içeren bir nesnedir. Bir işlem, V
doğal olana 1 ekler. Diğer işlem, P
doğal sayıyı 1 azaltır. Her iki etkinlik de atomiktir (yani a V
veya a ile aynı anda başka hiçbir işlem gerçekleştirilemez P
).
Doğal sayı 0 azaltılamadığından, P
0 içeren bir semaforda çağrı yapmak, sayının artık 0 olmadığı ve P
başarılı bir şekilde (ve atomik olarak) yürütülebildiği bir ana kadar çağrı sürecinin (/ evre) yürütülmesini engeller .
Diğer cevaplarda belirtildiği gibi semaforlar, belirli bir kaynağa erişimi maksimum (ancak değişken) sayıda işlemle sınırlamak için kullanılabilir.
Fikri anlamaya yardımcı olacak görselleştirmeyi oluşturdum. Semafor, çok iş parçacıklı bir ortamda ortak bir kaynağa erişimi denetler.
ExecutorService executor = Executors.newFixedThreadPool(7);
Semaphore semaphore = new Semaphore(4);
Runnable longRunningTask = () -> {
boolean permit = false;
try {
permit = semaphore.tryAcquire(1, TimeUnit.SECONDS);
if (permit) {
System.out.println("Semaphore acquired");
Thread.sleep(5);
} else {
System.out.println("Could not acquire semaphore");
}
} catch (InterruptedException e) {
throw new IllegalStateException(e);
} finally {
if (permit) {
semaphore.release();
}
}
};
// execute tasks
for (int j = 0; j < 10; j++) {
executor.submit(longRunningTask);
}
executor.shutdown();
Çıktı
Semaphore acquired
Semaphore acquired
Semaphore acquired
Semaphore acquired
Could not acquire semaphore
Could not acquire semaphore
Could not acquire semaphore
Makaleden örnek kod
Bir donanım veya yazılım bayrağı. Çoklu görev sistemlerinde, bir semafor, ortak bir kaynağın durumunu gösteren bir değere sahip bir değişkendir. Kaynağa ihtiyaç duyan bir süreç, semaforu kontrol ederek kaynak durumunu belirler ve sonra nasıl ilerleyeceğine karar verir.
Semaforlar iplik sınırlayıcılar gibi davranır.
Örnek: 100 iş parçacığına sahip bir havuzunuz varsa ve bazı DB işlemleri gerçekleştirmek istiyorsanız. 100 iş parçacığı belirli bir zamanda DB'ye erişirse, DB'de kilitleme sorunu olabilir, böylece bir seferde yalnızca sınırlı iş parçacığına izin veren semafor kullanabiliriz. Bir iş parçacığı acquire()
yöntemi çağırdığında, erişim elde edecek ve release()
yöntemi çağırdıktan sonra , sonraki iş parçacığı erişim elde etmek için erişim serbest bırakacaktır.
package practice;
import java.util.concurrent.Semaphore;
public class SemaphoreExample {
public static void main(String[] args) {
Semaphore s = new Semaphore(1);
semaphoreTask s1 = new semaphoreTask(s);
semaphoreTask s2 = new semaphoreTask(s);
semaphoreTask s3 = new semaphoreTask(s);
semaphoreTask s4 = new semaphoreTask(s);
semaphoreTask s5 = new semaphoreTask(s);
s1.start();
s2.start();
s3.start();
s4.start();
s5.start();
}
}
class semaphoreTask extends Thread {
Semaphore s;
public semaphoreTask(Semaphore s) {
this.s = s;
}
@Override
public void run() {
try {
s.acquire();
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName()+" Going to perform some operation");
s.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Herkesin tuvalete gitmeye çalıştığını ve banyoya sadece belirli sayıda anahtar olduğunu düşünün. Şimdi yeterli anahtar kalmadıysa, o kişinin beklemesi gerekir. Bu yüzden semaforu, farklı süreçlerin (banyo müdavimlerinin) erişim isteyebileceği banyolar için kullanılabilen anahtar kümesini (sistem kaynakları) temsil ediyor olarak düşünün.
Şimdi aynı anda tuvalete gitmeye çalışan iki işlem hayal edin. Bu iyi bir durum değildir ve bunu önlemek için semaforlar kullanılır. Ne yazık ki, semafor gönüllü bir mekanizmadır ve süreçler (banyo müdavimlerimiz) onu görmezden gelebilir (yani anahtarlar olsa bile, birisi hala kapıyı açıp açabilir).
İkili / muteks ve sayma semaforları arasında da farklılıklar vardır.
Http://www.cs.columbia.edu/~jae/4118/lect/L05-ipc.html adresindeki ders notlarına göz atın .
Bu eski bir sorudur, ancak semaforun en ilginç kullanımlarından biri okuma / yazma kilididir ve açıkça belirtilmemiştir.
R / w kilitleri basit bir şekilde çalışır: bir okuyucu için bir izin ve yazarlar için tüm izinleri tüketir. Gerçekten de, ar / w kilidinin önemsiz bir uygulamasıdır, ancak bir şişe boynu haline gelebilen, yine de muteks veya kilitten önemli ölçüde daha iyi olan okumada (aslında iki kez) meta veri değişikliği gerektirir.
Diğer bir dezavantaj, semafor adil değilse veya yazarlar birden fazla istekte izin almadıkça yazarların da kolayca başlatılabilmesidir, bu durumda kendi aralarında açık bir mutekse ihtiyaç duyarlar.
Daha okuma :
Semafor bir kaynağı kilitlemenin bir yoludur, böylece bir kod parçası yürütülürken, yalnızca bu kod parçasının o kaynağa erişimi garanti edilir. Bu, iki iş parçacığının aynı anda bir kaynağa erişmesini engeller ve bu da sorunlara neden olabilir.