Binder का थ्रेडिंग मॉडल, लोकल फ़ंक्शन कॉल की सुविधा देने के लिए डिज़ाइन किया गया है. भले ही, ये कॉल किसी रिमोट प्रोसेस के लिए हों. खास तौर पर, किसी नोड को होस्ट करने वाली हर प्रोसेस में, एक या उससे ज़्यादा बाइंडर थ्रेड का पूल होना चाहिए, ताकि उस प्रोसेस में होस्ट किए गए नोड के साथ लेन-देन किया जा सके.
सिंक्रोनस और एसिंक्रोनस लेन-देन
Binder, सिंक्रोनस और एसिंक्रोनस, दोनों तरह के लेन-देन की सुविधा देता है. यहां दिए गए सेक्शन में बताया गया है कि हर तरह का लेन-देन कैसे किया जाता है.
सिंक्रोनस लेन-देन
सिंक्रोनस लेन-देन तब तक ब्लॉक रहते हैं, जब तक उन्हें नोड पर नहीं किया जाता. साथ ही, कॉलर को उस लेन-देन का जवाब नहीं मिल जाता. यहां दी गई इमेज में दिखाया गया है कि सिंक्रोनस लेन-देन कैसे किया जाता है:
पहली इमेज. सिंक्रोनस लेन-देन.
सिंक्रोनस लेन-देन करने के लिए, बाइंडर यह काम करता है:
- टारगेट थ्रेडपूल (T2 और T3) में मौजूद थ्रेड, आने वाले काम का इंतज़ार करने के लिए, कर्नल ड्राइवर को कॉल करते हैं.
- कर्नल को नया लेन-देन मिलता है. इसके बाद, वह लेन-देन करने के लिए, टारगेट प्रोसेस में मौजूद किसी थ्रेड (T2) को जगाता है.
- कॉल करने वाला थ्रेड (T1) ब्लॉक हो जाता है और जवाब का इंतज़ार करता है.
- टारगेट प्रोसेस, लेन-देन करती है और जवाब देती है.
- टारगेट प्रोसेस (T2) में मौजूद थ्रेड, नए काम का इंतज़ार करने के लिए, कर्नल ड्राइवर को वापस कॉल करता है.
एसिंक्रोनस लेन-देन
एसिंक्रोनस लेन-देन, पूरा होने तक ब्लॉक नहीं होते. लेन-देन को कर्नल में पास करने के तुरंत बाद, कॉल करने वाला थ्रेड अनब्लॉक हो जाता है. यहां दी गई इमेज में दिखाया गया है कि एसिंक्रोनस लेन-देन कैसे किया जाता है:
दूसरी इमेज. एसिंक्रोनस लेन-देन.
- टारगेट थ्रेडपूल (T2 और T3) में मौजूद थ्रेड, आने वाले काम का इंतज़ार करने के लिए, कर्नल ड्राइवर को कॉल करते हैं.
- कर्नल को नया लेन-देन मिलता है. इसके बाद, वह लेन-देन करने के लिए, टारगेट प्रोसेस में मौजूद किसी थ्रेड (T2) को जगाता है.
- कॉल करने वाला थ्रेड (T1) काम करना जारी रखता है.
- टारगेट प्रोसेस, लेन-देन करती है और जवाब देती है.
- टारगेट प्रोसेस (T2) में मौजूद थ्रेड, नए काम का इंतज़ार करने के लिए, कर्नल ड्राइवर को वापस कॉल करता है.
सिंक्रोनस या एसिंक्रोनस फ़ंक्शन की पहचान करना
AIDL फ़ाइल में oneway के तौर पर मार्क किए गए फ़ंक्शन, एसिंक्रोनस होते हैं. उदाहरण के लिए:
oneway void someCall();
अगर किसी फ़ंक्शन को oneway के तौर पर मार्क नहीं किया गया है, तो वह सिंक्रोनस फ़ंक्शन होता है. भले ही, फ़ंक्शन void दिखाता हो.
एसिंक्रोनस लेन-देन का क्रम से होना
Binder, किसी भी एक नोड से होने वाले एसिंक्रोनस लेन-देन को क्रम से करता है. यहां दी गई इमेज में दिखाया गया है कि बाइंडर, एसिंक्रोनस लेन-देन को क्रम से कैसे करता है:
तीसरी इमेज. एसिंक्रोनस लेन-देन का क्रम से होना.
- टारगेट थ्रेडपूल (B1 और B2) में मौजूद थ्रेड, आने वाले काम का इंतज़ार करने के लिए, कर्नल ड्राइवर को कॉल करते हैं.
- एक ही नोड (N1) पर दो लेन-देन (T1 और T2) कर्नल को भेजे जाते हैं.
- कर्नल को नए लेन-देन मिलते हैं. चूंकि, ये लेन-देन एक ही नोड (N1) से किए गए हैं, इसलिए कर्नल इन्हें क्रम से करता है.
- किसी दूसरे नोड (N2) पर किया गया एक और लेन-देन, कर्नल को भेजा जाता है.
- कर्नल को तीसरा लेन-देन मिलता है. इसके बाद, वह लेन-देन करने के लिए, टारगेट प्रोसेस में मौजूद किसी थ्रेड (B2) को जगाता है.
- टारगेट प्रोसेस, हर लेन-देन करती है और जवाब देती है.
नेस्ट किए गए लेन-देन
सिंक्रोनस लेन-देन को नेस्ट किया जा सकता है. इसका मतलब है कि कोई थ्रेड, लेन-देन को हैंडल करते समय, नया लेन-देन कर सकता है. नेस्ट किया गया लेन-देन, किसी दूसरी प्रोसेस या उसी प्रोसेस के लिए हो सकता है जिससे आपको मौजूदा लेन-देन मिला है. यह व्यवहार, लोकल फ़ंक्शन कॉल जैसा होता है. उदाहरण के लिए, मान लें कि आपके पास नेस्ट किए गए फ़ंक्शन वाला कोई फ़ंक्शन है:
def outer_function(x):
def inner_function(y):
def inner_inner_function(z):
अगर ये लोकल कॉल हैं, तो इन्हें एक ही थ्रेड पर किया जाता है.
खास तौर पर, अगर inner_function का कॉलर, उस नोड को होस्ट करने वाली प्रोसेस
भी है जो inner_inner_function को लागू करता है, तो
inner_inner_function को कॉल, उसी थ्रेड पर किया जाता है.
यहां दी गई इमेज में दिखाया गया है कि बाइंडर, नेस्ट किए गए लेन-देन को कैसे हैंडल करता है:
चौथी इमेज. नेस्ट किए गए लेन-देन.
- थ्रेड A1,
foo()को चलाने का अनुरोध करता है. - इस अनुरोध के तहत, थ्रेड B1,
bar()को चलाता है. इसे A, उसी थ्रेड A1 पर चलाता है.
यहां दी गई इमेज में, थ्रेड के एक्ज़ीक्यूशन को दिखाया गया है. इसमें bar() को लागू करने वाला नोड, किसी दूसरी प्रोसेस में है:
पांचवीं इमेज. अलग-अलग प्रोसेस में नेस्ट किए गए लेन-देन.
- थ्रेड A1,
foo()को चलाने का अनुरोध करता है. - इस अनुरोध के तहत, थ्रेड B1,
bar()को चलाता है. इसे किसी दूसरे थ्रेड C1 में चलाया जाता है.
यहां दी गई इमेज में दिखाया गया है कि लेन-देन चेन में, थ्रेड एक ही प्रोसेस को कैसे फिर से इस्तेमाल करता है:
छठी इमेज. नेस्ट किए गए लेन-देन में, थ्रेड का फिर से इस्तेमाल.
- प्रोसेस A, प्रोसेस B को कॉल करती है.
- प्रोसेस B, प्रोसेस C को कॉल करती है.
- इसके बाद, प्रोसेस C, प्रोसेस A को वापस कॉल करती है. साथ ही, कर्नल, लेन-देन चेन का हिस्सा होने वाली प्रोसेस A में मौजूद थ्रेड A1 को फिर से इस्तेमाल करता है.
एसिंक्रोनस लेन-देन के लिए, नेस्टिंग की कोई भूमिका नहीं होती. क्लाइंट, एसिंक्रोनस लेन-देन के नतीजे का इंतज़ार नहीं करता. इसलिए, नेस्टिंग नहीं होती. अगर एसिंक्रोनस लेन-देन का हैंडलर, उस प्रोसेस को कॉल करता है जिसने एसिंक्रोनस लेन-देन किया है, तो उस लेन-देन को उस प्रोसेस में मौजूद किसी भी खाली थ्रेड पर हैंडल किया जा सकता है.
डेडलॉक से बचना
यहां दी गई इमेज में, एक सामान्य डेडलॉक दिखाया गया है:
सातवीं इमेज. सामान्य डेडलॉक.
- प्रोसेस A, म्यूटेक्स MA लेती है और प्रोसेस B को बाइंडर कॉल (T1) करती है. प्रोसेस B भी म्यूटेक्स MB लेने की कोशिश करती है.
- साथ ही, प्रोसेस B, म्यूटेक्स MB लेती है और प्रोसेस A को बाइंडर कॉल (T2) करती है. प्रोसेस A, म्यूटेक्स MA लेने की कोशिश करती है.
अगर ये लेन-देन ओवरलैप होते हैं, तो हर लेन-देन में, अपनी प्रोसेस में म्यूटेक्स लेने की संभावना होती है. ऐसा तब होता है, जब दूसरे प्रोसेस के म्यूटेक्स को रिलीज़ करने का इंतज़ार किया जा रहा हो. इससे डेडलॉक हो सकता है.
बाइंडर का इस्तेमाल करते समय डेडलॉक से बचने के लिए, बाइंडर कॉल करते समय कोई लॉक न रखें.
लॉक ऑर्डर करने के नियम और डेडलॉक
एक ही एक्ज़ीक्यूशन एनवायरमेंट में, लॉक ऑर्डर करने के नियम की मदद से अक्सर डेडलॉक से बचा जाता है. हालांकि, प्रोसेस और कोडबेस के बीच कॉल करते समय, खास तौर पर कोड अपडेट होने पर, ऑर्डर करने के नियम को बनाए रखना और कोऑर्डिनेट करना मुमकिन नहीं है.
एक म्यूटेक्स और डेडलॉक
नेस्ट किए गए लेन-देन के साथ, प्रोसेस B, म्यूटेक्स रखने वाली प्रोसेस A में मौजूद उसी थ्रेड को सीधे वापस कॉल कर सकती है. इसलिए, अनचाहे रिकर्शन की वजह से, एक म्यूटेक्स के साथ भी डेडलॉक होने की संभावना होती है.
एसिंक्रोनस कॉल और डेडलॉक
एसिंक्रोनस बाइंडर कॉल, पूरा होने तक ब्लॉक नहीं होते. हालांकि, आपको एसिंक्रोनस कॉल के लिए लॉक रखने से भी बचना चाहिए. अगर लॉक रखा जाता है, तो एकतरफ़ा कॉल को गलती से सिंक्रोनस कॉल में बदलने पर, आपको लॉक करने से जुड़ी समस्याएं आ सकती हैं.
एक बाइंडर थ्रेड और डेडलॉक
बाइंडर का लेन-देन मॉडल, रीएंट्रेंसी की अनुमति देता है. इसलिए, अगर किसी प्रोसेस में एक बाइंडर थ्रेड है, तब भी आपको लॉक करने की ज़रूरत होती है. उदाहरण के लिए, मान लें कि एक थ्रेड वाली प्रोसेस A में, किसी सूची पर इटरेट किया जा रहा है. सूची में मौजूद हर आइटम के लिए, आउटगोइंग बाइंडर लेन-देन किया जाता है. अगर कॉल किए जा रहे फ़ंक्शन के लागू होने से, प्रोसेस A में होस्ट किए गए नोड के लिए नया बाइंडर लेन-देन होता है, तो उस लेन-देन को उसी थ्रेड पर हैंडल किया जाता है जो सूची पर इटरेट कर रहा था. अगर उस लेन-देन के लागू होने से, उसी सूची में बदलाव होता है, तो बाद में सूची पर इटरेट करते समय आपको समस्याएं आ सकती हैं.
थ्रेडपूल का साइज़ कॉन्फ़िगर करना
जब किसी सेवा के कई क्लाइंट होते हैं, तो थ्रेडपूल में ज़्यादा थ्रेड जोड़ने से, कॉन्टेंशन कम हो सकता है और ज़्यादा कॉल को एक साथ हैंडल किया जा सकता है. कॉन्करेंसी को सही तरीके से हैंडल करने के बाद, ज़्यादा थ्रेड जोड़े जा सकते हैं. ज़्यादा थ्रेड जोड़ने से एक समस्या यह हो सकती है कि शांत वर्कलोड के दौरान, कुछ थ्रेड का इस्तेमाल न किया जाए.
थ्रेड, कॉन्फ़िगर की गई ज़्यादा से ज़्यादा संख्या तक, मांग पर स्पॉन होते हैं. बाइंडर थ्रेड स्पॉन होने के बाद, वह तब तक चालू रहता है, जब तक उसे होस्ट करने वाली प्रोसेस खत्म नहीं हो जाती.
libbinder लाइब्रेरी में, डिफ़ॉल्ट रूप से 15 थ्रेड होते हैं. इस वैल्यू को बदलने के लिए, setThreadPoolMaxThreadCount का इस्तेमाल करें:
using ::android::ProcessState;
ProcessState::self()->setThreadPoolMaxThreadCount(size_t maxThreads);