Mutex örneği / öğretici? [kapalı]


176

Çok iş parçacıklığında yeniyim ve mutekslerin nasıl çalıştığını anlamaya çalışıyordum. Çok fazla Google yaptım ama yine de nasıl çalıştığına dair bazı şüpheler bıraktı çünkü kilitlemenin çalışmadığı kendi programımı oluşturdum.

Muteksin kesinlikle sezgisel olmayan bir sözdizimi, muteks pthread_mutex_lock( &mutex1 );kilitli gibi görünüyor, gerçekten kilitlemek istediğim şey başka bir değişken olduğunda. Bu sözdizimi, bir muteksin kilitlenmesinin, muteksin kilidi açılana kadar bir kod bölgesini kilitlediği anlamına mı geliyor? O zaman evreleri bölgenin kilitli olduğunu nasıl bilebilir? [ GÜNCELLEME: Konular, bölgenin Bellek Eskrimiyle kilitlendiğini bilir ]. Ve böyle bir fenomenin eleştirel bölüm olarak adlandırılması gerekmez mi? [ GÜNCELLEME: Kritik bölüm nesneleri yalnızca Windows'ta kullanılabilir; burada nesneler mutekslerden daha hızlıdır ve yalnızca onu uygulayan evre tarafından görülebilir. Aksi takdirde, kritik bölüm sadece bir muteks tarafından korunan kod alanını ifade eder ]

Kısacası, mümkün olan en basit muteks örnek programı ve nasıl çalıştığının mantığıyla ilgili mümkün olan en basit açıklama konusunda yardımcı olabilir misiniz ? Eminim bu birçok yeni yeniye yardımcı olacaktır .


2
Basit bir eğiticiye olan ihtiyacı vurgulamaya devam etmek (konuları, tbb veya pthreads'yi güçlendirmek olsun): Karışıklık örnekleri: 1. stackoverflow.com/questions/3528877/… 2. stackoverflow.com/questions/2979525/… 3. stackoverflow.com/questions/2095977/to-mutex-or-not-to-mutex 4. stackoverflow.com/questions/3931026/… 5. stackoverflow.com/questions/1525189/…
Nav

1
Bunu saldırgan bir şekilde kastetmiyorum, ancak son yorumunuzun bana önerdiği şey, daha az analojiye ve muteksin nasıl çalıştığına ve neden onlara ihtiyacımız olduğuna ilişkin daha iyi bir teknik açıklamaya ihtiyacımız olduğu.
San Jacinto

@San: Hiçbir suç alınmadı :) Yorumlarım sadece bir aceminin muteksilerin en kısa, en net açıklamasını alabileceğini önermekti. Yeni analoji için birçok analoji kafa karıştırıcı olabilir, bu nedenle farklı analojiler ayrı tutulmalıdır. Ques ve ans yayınlamamın tüm nedeni, bir acemi olarak, uzun açıklamaları ve kod örneklerini okumak için bir acı buldum. Başka kimsenin acıdan geçmesini istemem.
Nav

2
@Cory: Bu cevap geliştirilebilirse, önerilerinizi almaktan memnuniyet duyarım. Bir çok insanın bunu yararlı bulduğuna sevindim. Size yardımcı olmadıysa, diğer muteks eğiticilerine işaret eden diğer insanlardan da cevaplar vardır. Neden bu kadar olumsuz olmalıyım?
Nav

Yanıtlar:


278

İşte konsepti dünyadaki yeni insanlara açıklamak için alçakgönüllü girişimim: (blogumda da renk kodlu bir versiyon )

Bir çok insan sevdikleriyle konuşmak için yalnız bir telefon kulübesine koşuyor (cep telefonları yok). Standın kapı kolunu yakalayan ilk kişi, telefonu kullanmasına izin verilen kişidir. Telefonu kullandığı sürece kapının tutamağına tutunmaya devam etmelidir, aksi takdirde bir başkası kolu tutacak, onu fırlatacak ve karısıyla konuşacaktır :) Böyle bir kuyruk sistemi yoktur. Kişi çağrısını bitirdiğinde, stanttan çıkar ve kapı kolundan ayrılırsa, kapı kolunu tutacak bir sonraki kişinin telefonu kullanmasına izin verilir.

Bir iş parçacığı : Her kişi mutex geçerli: kapı kolu kilit kişinin eli: is kaynak geçerli: Telefon


Aynı anda diğer iş parçacıkları tarafından değiştirilmemesi gereken bazı kod satırlarını yürütmesi gereken herhangi bir iş parçacığı (karısıyla konuşmak için telefonu kullanarak), önce bir muteks üzerinde bir kilit edinmelidir (kabinin kapı kolunu tutarak) ). Ancak bundan sonra bir iş parçacığı bu kod satırlarını çalıştırabilir (telefon görüşmesi yapabilir).

İş parçacığı bu kodu yürüttükten sonra, başka bir iş parçacığının muteks üzerinde kilit alabilmesi için muteks üzerindeki kilidi serbest bırakması gerekir (diğer kişiler telefon kabinine erişebilir).

[ Mutex'e sahip olma kavramı gerçek dünyaya özel erişimi düşünürken biraz saçma, ancak programlama dünyasında diğer ipliklerin bir iş parçacığının zaten bazı kod satırlarını yürüttüğünü 'görmesini' sağlayan başka bir yol yoktu. Özyinelemeli muteksler vb. Kavramlar vardır, ancak bu örnek size yalnızca temel kavramı göstermek içindir. Umarım örnek size kavramın net bir resmini verir. ]

C ++ 11 iş parçacığıyla:

#include <iostream>
#include <thread>
#include <mutex>

std::mutex m;//you can use std::lock_guard if you want to be exception safe
int i = 0;

void makeACallFromPhoneBooth() 
{
    m.lock();//man gets a hold of the phone booth door and locks it. The other men wait outside
      //man happily talks to his wife from now....
      std::cout << i << " Hello Wife" << std::endl;
      i++;//no other thread can access variable i until m.unlock() is called
      //...until now, with no interruption from other men
    m.unlock();//man lets go of the door handle and unlocks the door
}

int main() 
{
    //This is the main crowd of people uninterested in making a phone call

    //man1 leaves the crowd to go to the phone booth
    std::thread man1(makeACallFromPhoneBooth);
    //Although man2 appears to start second, there's a good chance he might
    //reach the phone booth before man1
    std::thread man2(makeACallFromPhoneBooth);
    //And hey, man3 also joined the race to the booth
    std::thread man3(makeACallFromPhoneBooth);

    man1.join();//man1 finished his phone call and joins the crowd
    man2.join();//man2 finished his phone call and joins the crowd
    man3.join();//man3 finished his phone call and joins the crowd
    return 0;
}

Kullanarak derleyin ve çalıştırın g++ -std=c++0x -pthread -o thread thread.cpp;./thread

Açıkça lockve yerine kullanmak yerine , sağladığı avantaj için kapsamlı bir kilit kullanıyorsanız, burada gösterildiği gibiunlock köşeli ayraç kullanabilirsiniz . Kapsamlı kilitler, hafif bir performans yüküne sahiptir.


2
@San: Dürüst olacağım; Evet, ayrıntıları (akışla) eksiksiz bir yeni başlayana açıklamak için elinizden gelenin en iyisini yapmaya çalıştığınızı seviyorum. ANCAK, (lütfen beni yanlış anlamayın) bu yazının amacı kavramı kısa bir açıklamaya koymaktı (çünkü diğer cevaplar uzun öğreticilere işaret ediyordu ). Umarım tüm cevabınızı kopyalamanızı ve ayrı bir cevap olarak göndermenizi istersem umursamazsınız? Böylece cevabınızı göstermek için cevabımı geri alabilir ve düzenleyebilirim.
Nav

2
@Tom Bu durumda, o mutekse erişmemelisiniz. Üzerindeki işlemler kapsüllenmeli, böylece koruduğu her neyse bu tür bir ahmaklıktan korunmalıdır. Kitaplığın açık API'sını kullandığınızda, kitaplığın iş parçacığı açısından güvenli olacağı garanti edilirse, kendi paylaşılan öğelerinizi korumak için tamamen farklı bir muteks eklemeniz güvenlidir. Aksi takdirde, önerdiğiniz gibi gerçekten yeni bir kapı kolu eklersiniz.
San Jacinto

2
Demek istediğimi genişletmek için, yapmak istediğiniz şey standın etrafına daha büyük, daha büyük bir oda eklemek. Odada tuvalet ve duş da olabilir. Odada aynı anda sadece 1 kişiye izin verildiğini varsayalım. Odanın tasarımını yapmalısınız ki bu odada, telefon kulübesine çok benzer şekilde odaya girişi koruyan bir kulplu bir kapı olmalıdır. Şimdi, fazla muteksiniz olsa bile, telefon kabinini herhangi bir projede yeniden kullanabilirsiniz. Başka bir seçenek, odadaki her cihaz için kilitleme mekanizmalarını ortaya çıkarmak ve oda sınıfındaki kilitleri yönetmek olacaktır. Her iki durumda da, aynı nesneye yeni kilitler eklemezsiniz.
San Jacinto

8
C ++ 11 iş parçacığı örneğiniz yanlış . Yani TBB biridir ipucu adı olduğu kapsamlı kilidi .
Jonathan Wakely

3
İkisinin de farkındayım, @Jonathan. Yazdığım cümleyi kaçırmış gibisin (could've shown scoped locking by not using acquire and release - which also is exception safe -, but this is clearer. Kapsamlı kilitleme kullanımına gelince, ne tür bir uygulama oluşturduklarına bağlı olarak geliştiriciye bağlıdır. Bu cevap, muteks kavramının temel anlayışına değinmek ve tüm karmaşıklıklarına girmemekti, bu nedenle yorumlarınız ve bağlantılarınız açıktır, ancak bu eğitimin kapsamı dışındadır.
Nav

41

Bir muteks diğer sorunları çözmek için kullanılabilirken, var olmalarının temel nedeni karşılıklı dışlama sağlamak ve böylece bir yarış koşulu olarak bilinen şeyi çözmek. İki (veya daha fazla) iş parçacığı veya işlem aynı değişkene aynı anda erişmeye çalıştığında, bir yarış durumu potansiyeline sahibiz. Aşağıdaki kodu düşünün

//somewhere long ago, we have i declared as int
void my_concurrently_called_function()
{
  i++;
}

Bu işlevin iç kısımları çok basit görünüyor. Bu sadece bir açıklama. Ancak, tipik bir sözde derleme dili eşdeğeri şunlar olabilir:

load i from memory into a register
add 1 to i
store i back into memory

Eşdeğer montaj dili komutlarının tümü i üzerinde artış işlemini gerçekleştirmek için gerekli olduğundan, i'yi artırmanın atmoik olmayan bir işlem olduğunu söylüyoruz. Bir atomik işlem, talimatın yürütülmesi başladıktan sonra bir garantinin kesintiye uğramamasıyla donanım üzerinde tamamlanabilen bir işlemdir. Artış i, 3 ​​atom komutundan oluşan bir zincirden oluşur. Birçok iş parçacığının işlevi çağırdığı eşzamanlı bir sistemde, iş parçacığı yanlış zamanda okuduğunda veya yazdığında sorunlar oluşur. Aynı anda çalışan iki iş parçacığımız olduğunu ve birinin işlevi birbiri ardına çağırdığını düşünün. Ayrıca, 0 olarak başlattığımızı da söyleyelim. Ayrıca, çok sayıda kayıt bulunduğumuzu ve iki iş parçacığının tamamen farklı kayıtlar kullandığını varsayalım, böylece çarpışma olmayacak. Bu olayların gerçek zamanlaması şunlar olabilir:

thread 1 load 0 into register from memory corresponding to i //register is currently 0
thread 1 add 1 to a register //register is now 1, but not memory is 0
thread 2 load 0 into register from memory corresponding to i
thread 2 add 1 to a register //register is now 1, but not memory is 0
thread 1 write register to memory //memory is now 1
thread 2 write register to memory //memory is now 1

Olan şey, i'yi aynı anda arttıran iki iş parçacığımız var, işlevimiz iki kez çağrılıyor, ancak sonuç bu gerçekle tutarsız. Fonksiyon sadece bir kez çağrılmış gibi görünüyor. Bunun nedeni, atomikliğin makine düzeyinde "kırılmasıdır", yani dişlerin birbirini kesebileceği veya yanlış zamanlarda birlikte çalışabileceği anlamına gelir.

Bunu çözmek için bir mekanizmaya ihtiyacımız var. Yukarıdaki talimatlara biraz sipariş vermeliyiz. Yaygın bir mekanizma, biri hariç tüm dişleri engellemektir. Pthread mutex bu mekanizmayı kullanır.

Paylaşılan değerleri aynı anda diğer iş parçacıkları tarafından güvensiz olarak değiştirebilecek bazı kod satırlarını yürütmesi gereken her iş parçacığı (karısıyla konuşmak için telefonu kullanarak), önce bir muteks üzerinde kilit kazanmalıdır. Bu şekilde, paylaşılan verilere erişim gerektiren tüm evreler muteks kilidinden geçmelidir. Ancak bundan sonra bir iş parçacığı kodu yürütebilir. Kodun bu bölümüne kritik bölüm denir.

İş parçacığı kritik bölümü yürüttükten sonra, başka bir iş parçacığının muteks üzerinde kilit alabilmesi için muteks üzerindeki kilidi serbest bırakması gerekir.

Gerçek, fiziksel nesnelere münhasır erişim isteyen insanlar düşünülürken muteksi olma kavramı biraz garip görünüyor, ancak programlama yaparken kasıtlı olmalıyız. Eşzamanlı iş parçacıkları ve süreçler, yaptığımız sosyal ve kültürel yetiştiriciliğe sahip değildir, bu yüzden verileri güzel bir şekilde paylaşmaya zorlamalıyız.

Teknik olarak konuşursak, bir muteks nasıl çalışır? Daha önce bahsettiğimiz aynı yarış koşullarından muzdarip değil mi? Pthread_mutex_lock () bir değişkenin basit bir artışından biraz daha karmaşık değil mi?

Teknik olarak, bize yardımcı olmak için bazı donanım desteğine ihtiyacımız var. Donanım tasarımcıları bize birden fazla şey yapan ancak atomik olduğu garanti edilen makine talimatları veriyorlar. Böyle bir talimatın klasik bir örneği test ve settir (TAS). Bir kaynak üzerinde bir kilit elde etmeye çalışırken, TAS'yi kullanarak bellekteki bir değerin 0 olup olmadığını kontrol edebiliriz. Eğer öyleyse, kaynağın kullanımda olduğunu gösteren işaretimiz olacaktır ve hiçbir şey yapmıyoruz (veya daha doğru bir şekilde Bir mekanizma tarafından bekleriz. Bir pthreads mutex, işletim sistemindeki özel bir sıraya girmemizi sağlar ve kaynak kullanılabilir olduğunda bizi bilgilendirir. Dümen sistemleri, durumu tekrar tekrar test etmemizi gerektirebilir. . Bellekteki değer 0 değilse, TAS başka bir talimat kullanmadan konumu 0 dışında bir değere ayarlar. O' Bize atomiklik kazandırmak için iki montaj talimatını 1'de birleştirmek gibi. Bu nedenle, değerin test edilmesi ve değiştirilmesi (eğer uygunsa) başladıktan sonra kesilemez. Muteksleri böyle bir talimatın üzerine inşa edebiliriz.

Not: bazı bölümler daha önceki bir cevaba benzer görünebilir. Düzenleme davetini kabul ettim, orijinal şeklini tercih etti, bu yüzden biraz da verbiage ile aşılanmış olanı koruyorum.


1
Çok teşekkür ederim San. Cevabınıza bağlandım :) Aslında, cevabımı + cevabınızı almanızı ve akışı tutmak için ayrı bir cevap olarak göndermeyi amaçlamıştım. Cevabımın herhangi bir bölümünü tekrar kullanmanızın bir sakıncası yok. Bunu zaten kendimiz için yapmıyoruz.
Nav

13

Bildiğim en iyi konu öğreticisi burada:

https://computing.llnl.gov/tutorials/pthreads/

Belirli bir uygulama yerine API hakkında yazılmış olmasını ve senkronizasyonu anlamanıza yardımcı olacak bazı basit basit örnekler vermesini seviyorum.


Kesinlikle iyi bir öğretici olduğunu kabul ediyorum, ancak tek bir sayfada çok fazla bilgi var ve programlar uzun. Gönderdiğim soru, yeni başlayanların muteksleri öğrenmek ve sezgisel olmayan sözdiziminin nasıl çalıştığını anlamak için basit bir yol bulacağı "bir hayalim var" konuşmasının muteks versiyonudur (bu, tüm derslerde eksik olan tek bir açıklamadır) .
Nav

7

Geçenlerde bu yazı tökezledi ve standart kütüphanenin c ++ 11 mutex (yani std :: mutex) için güncellenmiş bir çözüm gerektiğini düşünüyorum.

Aşağıda bazı kod yapıştırdım (mutex ile ilk adımlar - ben HANDLE, SetEvent, WaitForMultipleObjects vb win32 üzerinde eşzamanlılık öğrendim).

Std :: mutex ve arkadaşları ile ilk denemem olduğundan, yorumları, önerileri ve geliştirmeleri görmek isterim!

#include <condition_variable>
#include <mutex>
#include <algorithm>
#include <thread>
#include <queue>
#include <chrono>
#include <iostream>


int _tmain(int argc, _TCHAR* argv[])
{   
    // these vars are shared among the following threads
    std::queue<unsigned int>    nNumbers;

    std::mutex                  mtxQueue;
    std::condition_variable     cvQueue;
    bool                        m_bQueueLocked = false;

    std::mutex                  mtxQuit;
    std::condition_variable     cvQuit;
    bool                        m_bQuit = false;


    std::thread thrQuit(
        [&]()
        {
            using namespace std;            

            this_thread::sleep_for(chrono::seconds(5));

            // set event by setting the bool variable to true
            // then notifying via the condition variable
            m_bQuit = true;
            cvQuit.notify_all();
        }
    );


    std::thread thrProducer(
        [&]()
        {
            using namespace std;

            int nNum = 13;
            unique_lock<mutex> lock( mtxQuit );

            while ( ! m_bQuit )
            {
                while( cvQuit.wait_for( lock, chrono::milliseconds(75) ) == cv_status::timeout )
                {
                    nNum = nNum + 13 / 2;

                    unique_lock<mutex> qLock(mtxQueue);
                    cout << "Produced: " << nNum << "\n";
                    nNumbers.push( nNum );
                }
            }
        }   
    );

    std::thread thrConsumer(
        [&]()
        {
            using namespace std;
            unique_lock<mutex> lock(mtxQuit);

            while( cvQuit.wait_for(lock, chrono::milliseconds(150)) == cv_status::timeout )
            {
                unique_lock<mutex> qLock(mtxQueue);
                if( nNumbers.size() > 0 )
                {
                    cout << "Consumed: " << nNumbers.front() << "\n";
                    nNumbers.pop();
                }               
            }
        }
    );

    thrQuit.join();
    thrProducer.join();
    thrConsumer.join();

    return 0;
}

1
Süper! Gönderdiğiniz için teşekkürler. Daha önce de bahsettiğim gibi amacım sadece muteks kavramını açıklamaktı. Diğer tüm eğiticiler, üretici tüketicisi ve koşul değişkenleri vb. Eklenmiş kavramlarla çok zorlaştırdı, bu da yeryüzünde neler olduğunu anlamamı zorlaştırdı.
Nav

4

Fonksiyon pthread_mutex_lock()ya kazanır Muteksleri kadar iplik elde edilebilir çağıran iş parçacığı veya bloklar için muteksi. İlgili pthread_mutex_unlock()muteksi serbest bırakır.

Muteksi bir kuyruk olarak düşünün; muteksi elde etmeye çalışan her iş parçacığı kuyruğun sonuna yerleştirilir. Bir iş parçacığı muteksi serbest bıraktığında, sıradaki bir sonraki iş parçacığı çıkar ve şimdi çalışır.

Bir kritik bölüm deterministik olmayan mümkündür kod bir bölgesi anlamına gelir. Bu genellikle birden çok iş parçacığının paylaşılan bir değişkene erişmeye çalıştığı için olur. Bir tür senkronizasyon gerçekleşinceye kadar kritik bölüm güvenli değildir. Muteks kilidi, senkronizasyonun bir biçimidir.


1
Bir sonraki denemenin tam olarak gireceği garanti ediliyor mu?
Arsen Mkrtchyan

1
@Arsen Garanti yok. Bu sadece yararlı bir benzetme.
chrisaycock

3

Muteks tarafından korunan alanı kullanmadan önce muteks değişkenini kontrol etmeniz gerekir. Böylece pthread_mutex_lock () işleviniz (uygulamaya bağlı olarak) mutex1 serbest bırakılıncaya kadar bekleyebilir veya başka biri zaten kilitlemişse kilidin alınamayacağını gösteren bir değer döndürebilir.

Mutex gerçekten sadece basitleştirilmiş bir semafor. Onları okuyup anlarsanız, muteksleri anlarsınız. SO'da muteksler ve semaforlar ile ilgili birkaç soru vardır. İkili semafor ve muteks arasındaki fark , Mutex'i ne zaman ve ne zaman semafor kullanmalıyız vb. İlk bağlantıdaki tuvalet örneği, aklınıza gelebilecek kadar iyi bir örnektir. Tüm kod, anahtarın mevcut olup olmadığını ve anahtarın saklı olup olmadığını kontrol etmektir. Gerçekten tuvaleti değil, anahtar rezerve dikkat edin.


1
pthread_mutex_lockbaşka biri kilidi tutarsa ​​geri dönemez. Bu durumda engeller ve bütün mesele budur. pthread_mutex_trylockkilit basılı tutulursa dönecek olan işlevdir.
R .. GitHub BUZA YARDIMCI DURDUR

1
Evet, ilk başta bunun ne olduğunu bilmiyordum.
Makis

3

Kısa lateks muteks örneğini arayanlar için:

#include <mutex>

int main() {
    std::mutex m;

    m.lock();
    // do thread-safe stuff
    m.unlock();
}

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.