c++ tutorial for beginners




म्यूटेक्स उदाहरण/ट्यूटोरियल? (5)

मैं multithreading के लिए नया हूँ, और यह समझने की कोशिश कर रहा था कि mutexes कैसे काम करते हैं। बहुत सारे गुगलिंग और मुझे एक सभ्य ट्यूटोरियल मिला , लेकिन यह अभी भी कुछ काम करता है कि यह कैसे काम करता है क्योंकि मैंने अपना खुद का प्रोग्राम बनाया जिसमें लॉकिंग काम नहीं करती थी।

Mutex का एक बिल्कुल गैर-सहज ज्ञान युक्त वाक्यविन्यास pthread_mutex_lock( &mutex1 ); , जहां ऐसा लगता है कि म्यूटेक्स लॉक हो रहा है, जब मैं वास्तव में लॉक करना चाहता हूं तो कुछ अन्य चर है। क्या इस वाक्यविन्यास का मतलब है कि म्यूटेक्स लॉक करने तक म्यूटेक्स लॉक हो जाता है जब तक कि म्यूटेक्स अनलॉक नहीं हो जाता है? फिर धागे कैसे जानते हैं कि क्षेत्र बंद है? [ अद्यतन: थ्रेड जानते हैं कि मेमोरी बाड़ लगाने से क्षेत्र लॉक है ]। और ऐसी कोई घटना नहीं है जिसे महत्वपूर्ण खंड कहा जाता है? [ अद्यतन: क्रिटिकल सेक्शन ऑब्जेक्ट्स केवल विंडोज़ में उपलब्ध हैं, जहां ऑब्जेक्ट्स म्यूटेक्स से तेज़ होते हैं और केवल थ्रेड पर दिखाई देते हैं जो इसे लागू करता है। अन्यथा, महत्वपूर्ण खंड सिर्फ म्यूटेक्स द्वारा संरक्षित कोड के क्षेत्र को संदर्भित करता है ]

संक्षेप में, क्या आप सबसे सरल संभव म्यूटेक्स उदाहरण प्रोग्राम और यह कैसे काम करता है इस तर्क पर सबसे सरल संभव स्पष्टीकरण के साथ मदद कर सकते हैं? मुझे यकीन है कि यह कई अन्य नए लोगों की मदद करेगा।


आपको mutex द्वारा संरक्षित क्षेत्र का उपयोग करने से पहले mutex चर की जांच करनी होगी। तो आपका pthread_mutex_lock () कार्यान्वयन के आधार पर (कार्यान्वयन के आधार पर) प्रतीक्षा कर सकता है जब तक mutex1 जारी नहीं किया जाता है या एक मान वापस लौटाता है जो इंगित करता है कि लॉक नहीं प्राप्त किया जा सकता है अगर किसी और ने इसे पहले ही लॉक कर दिया है।

म्यूटेक्स वास्तव में सिर्फ एक सरलीकृत सेमफोर है। यदि आप उनके बारे में पढ़ते हैं और उन्हें समझते हैं, तो आप म्यूटेक्स समझते हैं। एसओ में म्यूटेक्स और सेमफोर के बारे में कई सवाल हैं। बाइनरी सेमफोर और म्यूटेक्स के बीच अंतर , हमें म्यूटेक्स का उपयोग कब करना चाहिए और हमें सैमफोर का उपयोग कब करना चाहिए । पहले लिंक में शौचालय का उदाहरण उतना ही अच्छा उदाहरण है जितना कि कोई सोच सकता है। सभी कोड यह जांचने के लिए है कि क्या कुंजी उपलब्ध है और यदि यह है, तो इसे सुरक्षित रखता है। ध्यान दें कि आप वास्तव में शौचालय को ही आरक्षित नहीं करते हैं, लेकिन कुंजी।


जबकि अन्य समस्याओं को हल करने के लिए एक म्यूटेक्स का उपयोग किया जा सकता है, वहीं प्राथमिक कारण यह है कि आपसी बहिष्कार प्रदान करना है और इस प्रकार रेस हालत के रूप में जाना जाता है। जब दो (या अधिक) धागे या प्रक्रिया एक ही चर को एक साथ करने का प्रयास कर रहे हैं, तो हमारे पास दौड़ की स्थिति की संभावना है। निम्नलिखित कोड पर विचार करें

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

इस समारोह के आंतरिक इतने सरल लगते हैं। यह केवल एक बयान है। हालांकि, एक ठेठ छद्म-असेंबली भाषा बराबर हो सकती है:

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

चूंकि समकक्ष असेंबली-भाषा निर्देशों को सभी पर वृद्धि कार्य करने की आवश्यकता होती है, हम कहते हैं कि बढ़ती हुई मैं एक गैर-परमाणु ऑपरेशन है। एक परमाणु ऑपरेशन वह है जिसे निर्देश पर निष्पादन शुरू होने के बाद हार्डवेयर पर पूरा किया जा सकता है। बढ़ते हुए मैं 3 परमाणु निर्देशों की एक श्रृंखला शामिल है। एक समवर्ती प्रणाली में जहां कई धागे फ़ंक्शन को कॉल कर रहे हैं, समस्या तब उत्पन्न होती है जब कोई थ्रेड गलत समय पर पढ़ता या लिखता है। कल्पना कीजिए कि हमारे पास simultaneoulsy चलने वाले दो धागे हैं और एक दूसरे के तुरंत बाद फ़ंक्शन को कॉल करता है। आइए यह भी कहें कि हमने 0 से शुरू किया है। यह भी मान लें कि हमारे पास बहुत सारे रजिस्ट्रार हैं और दोनों धागे पूरी तरह से अलग रजिस्टरों का उपयोग कर रहे हैं, इसलिए कोई टकराव नहीं होगा। इन घटनाओं का वास्तविक समय हो सकता है:

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

क्या हुआ है कि हमारे साथ दो धागे बढ़ते हैं, हमारे कार्य को दो बार बुलाया जाता है, लेकिन परिणाम उस तथ्य के साथ असंगत है। ऐसा लगता है कि समारोह केवल एक बार बुलाया गया था। ऐसा इसलिए है क्योंकि परमाणुता मशीन स्तर पर "टूटा" है, जिसका अर्थ है कि धागे एक-दूसरे को बाधित कर सकते हैं या गलत समय पर मिलकर काम कर सकते हैं।

इसे हल करने के लिए हमें एक तंत्र की आवश्यकता है। हमें उपरोक्त निर्देशों को कुछ आदेश देने की आवश्यकता है। एक आम तंत्र एक को छोड़कर सभी धागे को अवरुद्ध करना है। पाथ्रेड म्यूटेक्स इस तंत्र का उपयोग करता है।

कोई भी थ्रेड जो कोड की कुछ पंक्तियों को निष्पादित करना है जो एक ही समय में अन्य धागे द्वारा साझा मूल्यों को असुरक्षित रूप से संशोधित कर सकता है (फोन को अपनी पत्नी से बात करने के लिए) का उपयोग करके, पहले म्यूटेक्स पर लॉक प्राप्त करना चाहिए। इस तरह, किसी भी थ्रेड जिसे साझा डेटा तक पहुंच की आवश्यकता होती है उसे म्यूटेक्स लॉक से गुजरना होगा। केवल तब ही एक थ्रेड कोड निष्पादित करने में सक्षम होगा। कोड के इस खंड को एक महत्वपूर्ण खंड कहा जाता है।

एक बार धागे ने महत्वपूर्ण खंड को निष्पादित कर दिया है, तो इसे म्यूटेक्स पर लॉक जारी करना चाहिए ताकि एक और थ्रेड म्यूटेक्स पर लॉक प्राप्त कर सके।

म्यूटेक्स रखने की अवधारणा कुछ अजीब लगती है जब मनुष्य वास्तविक, भौतिक वस्तुओं के लिए विशेष पहुंच की मांग करते हैं लेकिन प्रोग्रामिंग करते समय, हमें जानबूझकर होना चाहिए। समवर्ती धागे और प्रक्रियाओं में सामाजिक और सांस्कृतिक उन्नयन नहीं होता है, इसलिए हमें उन्हें डेटा को अच्छी तरह से साझा करने के लिए मजबूर होना चाहिए।

तो तकनीकी रूप से बोलते हुए, एक म्यूटेक्स कैसे काम करता है? क्या यह उसी दौड़ की स्थिति से पीड़ित नहीं है जिसे हमने पहले उल्लेख किया था? Pthread_mutex_lock () थोड़ा और जटिल नहीं है कि एक चर की एक सरल वृद्धि?

तकनीकी रूप से बोलते हुए, हमें हमारी सहायता करने के लिए कुछ हार्डवेयर समर्थन की आवश्यकता है। हार्डवेयर डिजाइनर हमें मशीन निर्देश देते हैं जो एक से अधिक चीजें करते हैं लेकिन परमाणु होने के लिए गारंटीकृत हैं। इस तरह के निर्देश का एक उत्कृष्ट उदाहरण परीक्षण-और-सेट (टीएएस) है। संसाधन पर लॉक प्राप्त करने का प्रयास करते समय, हम टीएएस का उपयोग कर सकते हैं यह देखने के लिए जांच सकता है कि स्मृति में कोई मान 0 है या नहीं, यदि यह है, तो यह हमारा संकेत होगा कि संसाधन उपयोग में है और हम कुछ भी नहीं करते (या अधिक सटीक रूप से , हम कुछ तंत्र द्वारा प्रतीक्षा करते हैं। एक pthreads mutex हमें ऑपरेटिंग सिस्टम में एक विशेष कतार में डाल देगा और संसाधन उपलब्ध होने पर हमें सूचित करेगा। डंबर सिस्टम के लिए हमें एक कसकर स्पिन लूप करने की आवश्यकता हो सकती है, इस स्थिति का परीक्षण और अधिक) । यदि स्मृति में मान 0 नहीं है, तो TAS किसी भी अन्य निर्देशों का उपयोग किये बिना 0 के अलावा किसी अन्य स्थान पर स्थान सेट करता है। यह हमें परमाणुता देने के लिए दो विधानसभा निर्देशों को 1 में जोड़ना है। इस प्रकार, मूल्य का परीक्षण और परिवर्तन (यदि बदलना उचित है) इसे शुरू होने के बाद बाधित नहीं किया जा सकता है। हम इस तरह के निर्देश के शीर्ष पर म्यूटेक्स बना सकते हैं।

नोट: कुछ अनुभाग पहले के उत्तर के समान दिखाई दे सकते हैं। मैंने संपादित करने के लिए अपने निमंत्रण को स्वीकार कर लिया, वह मूल तरीके को पसंद करता था, इसलिए मैं जो कुछ भी रखता था उसे रखता हूं जो उसके कुछ अंश के साथ जुड़ जाता है।


मुझे पता है कि सबसे अच्छा धागा ट्यूटोरियल यहाँ है:

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

मुझे यह पसंद है कि यह एक विशेष कार्यान्वयन के बजाय एपीआई के बारे में लिखा गया है, और यह सिंक्रनाइज़ेशन को समझने में आपकी सहायता के लिए कुछ अच्छे सरल उदाहरण देता है।


मैंने हाल ही में इस पोस्ट पर ठोकर खाई और सोचा कि इसे मानक पुस्तकालय के सी ++ 11 म्यूटेक्स (अर्थात् std :: mutex) के लिए एक अद्यतन समाधान की आवश्यकता है।

मैंने नीचे कुछ कोड चिपकाया है (म्यूटेक्स के साथ मेरे पहले चरण - मैंने हैंडल, सेटवेन्ट, वेटफॉरमल्टिप्लिओ ऑब्जेक्ट इत्यादि के साथ Win32 पर समेकन सीखा)।

चूंकि यह std :: mutex और दोस्तों के साथ मेरा पहला प्रयास है, इसलिए मुझे टिप्पणियां, सुझाव और सुधार देखना अच्छा लगेगा!

#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;
}

दुनिया भर के नए लोगों को अवधारणा को समझाने के लिए मेरा विनम्र प्रयास यहां जाता है: (मेरे ब्लॉग पर एक रंग कोडित संस्करण भी)

बहुत से लोग अपने प्रियजनों से बात करने के लिए एक अकेले फोन बूथ (कोई मोबाइल) नहीं जाते हैं। बूथ के दरवाजे-हैंडल को पकड़ने वाला पहला व्यक्ति है, जिसे फोन का उपयोग करने की अनुमति है। जब तक वह फोन का उपयोग करता है तब तक उसे दरवाजे के हैंडल पर पकड़ना पड़ता है, अन्यथा कोई और संभाल पकड़ लेगा, उसे बाहर फेंक देगा और अपनी पत्नी से बात करेगा :) इस तरह की कोई कतार प्रणाली नहीं है। जब व्यक्ति अपनी कॉल खत्म कर देता है, तो बूथ से बाहर आता है और दरवाजे के हैंडल छोड़ देता है, अगले व्यक्ति को दरवाजे के हैंडल को पकड़ने के लिए फोन का उपयोग करने की अनुमति दी जाएगी।

एक धागा है: प्रत्येक व्यक्ति
म्यूटेक्स है: दरवाजा संभाल
ताला है: व्यक्ति का हाथ
संसाधन है: फोन

किसी भी धागे को कोड की कुछ पंक्तियों को निष्पादित करना होता है जिसे एक ही समय में अन्य धागे द्वारा संशोधित नहीं किया जाना चाहिए (फोन का उपयोग करके उसकी पत्नी से बात करने के लिए) को पहले म्यूटेक्स पर लॉक प्राप्त करना होगा (बूथ के दरवाजे के हैंडल को पकड़ना )। तभी तभी एक थ्रेड कोड की उन पंक्तियों को चलाने में सक्षम होगा (फोन कॉल कर रहा है)।

एक बार धागे ने उस कोड को निष्पादित कर लिया है, तो इसे म्यूटेक्स पर लॉक जारी करना चाहिए ताकि एक और धागा म्यूटेक्स पर लॉक प्राप्त कर सके (अन्य लोग फोन बूथ तक पहुंचने में सक्षम हैं)।

[ वास्तविक दुनिया की विशेष पहुंच पर विचार करते समय म्यूटेक्स होने की अवधारणा थोड़ा बेतुका है, लेकिन प्रोग्रामिंग दुनिया में मुझे लगता है कि अन्य धागे को देखने के लिए कोई अन्य तरीका नहीं था कि थ्रेड कोड की कुछ पंक्तियों को पहले ही निष्पादित कर रहा था। रिकर्सिव म्यूटेक्स इत्यादि की अवधारणाएं हैं, लेकिन यह उदाहरण केवल आपको मूल अवधारणा दिखाने के लिए था। उम्मीद है कि उदाहरण आपको अवधारणा की एक स्पष्ट तस्वीर देता है। ]

सी ++ 11 थ्रेडिंग के साथ:

#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;
}

g++ -std=c++0x -pthread -o thread thread.cpp;./thread का उपयोग करके संकलित करें और चलाएं

lock और unlock का स्पष्ट रूप से उपयोग करने के बजाय, आप यहां दिखाए गए ब्रैकेट का उपयोग कर सकते हैं, अगर आप इसे प्रदान किए गए लाभ के लिए स्कोप्ड लॉक का उपयोग कर रहे हैं। हालांकि स्कॉप्ड लॉक में थोड़ा प्रदर्शन ओवरहेड होता है।

टीबीबी के साथ: आपको निम्न कार्यक्रम चलाने के लिए TBB आवश्यकता होगी, लेकिन टीबीबी कोड पोस्ट करने का इरादा यह है कि आप सरल कोड को देखकर लॉकिंग और अनलॉक करने के अनुक्रम को समझते हैं (अधिग्रहण का उपयोग न करके स्कॉप्ड लॉकिंग दिखा सकता है और रिलीज - जो अपवाद भी सुरक्षित है - लेकिन यह स्पष्ट है)।

#include <iostream>
#include "/tbb/mutex.h"
#include "/tbb/tbb_thread.h"
using namespace tbb;

typedef mutex myMutex;
static myMutex sm;
int i = 0;

void someFunction() 
{ 
      //Note: Since a scoped lock is used below, you should know that you 
      //can specify a scope for the mutex using curly brackets, instead of 
      //using lock.acquire() and lock.release(). The lock will automatically 
      //get released when program control goes beyond the scope.
      myMutex::scoped_lock lock;//create a lock
      lock.acquire(sm);//Method acquire waits until it can acquire a lock on the mutex
         //***only one thread can access the lines from here...***
         ++i;//incrementing i is safe (only one thread can execute the code in this scope) because the mutex locked above protects all lines of code until the lock release.
         sleep(1);//simply creating a delay to show that no other thread can increment i until release() is executed
         std::cout<<"In someFunction "<<i<<"\n";
         //***...to here***
      lock.release();//releases the lock (duh!)      
}

int main()
{
   tbb_thread my_thread1(someFunction);//create a thread which executes 'someFunction'
   tbb_thread my_thread2(someFunction);
   tbb_thread my_thread3(someFunction);

   my_thread1.join();//This command causes the main thread (which is the 'calling-thread' in this case) to wait until thread1 completes its task.
   my_thread2.join();
   my_thread3.join();
}

ध्यान दें कि tbb_thread.h बहिष्कृत है। प्रतिस्थापन here दिखाया गया here





mutex