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 लेने की कोशिश करती है.
अगर ये लेन-देन एक-दूसरे से ओवरलैप होते हैं, तो हर लेन-देन अपनी प्रोसेस में एक म्यूटेक्स ले सकता है. ऐसा तब होता है, जब वह दूसरी प्रोसेस के म्यूटेक्स को रिलीज़ करने का इंतज़ार कर रहा हो. इससे डेडलॉक हो सकता है.
Binder का इस्तेमाल करते समय डेडलॉक से बचने के लिए, Binder कॉल करते समय किसी भी लॉक को होल्ड न करें.
ऑर्डर करने के नियमों और डेडलॉक को लॉक करना
एक ही एक्ज़ीक्यूशन एनवायरमेंट में, लॉक ऑर्डरिंग के नियम का इस्तेमाल करके अक्सर डेडलॉक से बचा जाता है. हालांकि, प्रोसेस और कोडबेस के बीच कॉल करते समय, खास तौर पर कोड अपडेट होने पर, क्रम से जुड़े नियम को बनाए रखना और उसे लागू करना मुश्किल होता है.
सिंगल म्यूटेक्स और डेडलॉक
नेस्ट किए गए लेन-देन की मदद से, प्रोसेस B सीधे तौर पर प्रोसेस A के उसी थ्रेड में वापस कॉल कर सकती है जिसमें म्यूटेक्स होता है. इसलिए, अचानक रिकर्सन होने की वजह से, एक ही म्यूटेक्स के साथ डेडलॉक की समस्या अब भी हो सकती है.
सिंक्रोनस कॉल और डेडलॉक
एसिंक्रोनस बाइंडर कॉल, पूरा होने तक ब्लॉक नहीं होते. हालांकि, आपको एसिंक्रोनस कॉल के लिए लॉक को होल्ड करने से भी बचना चाहिए. अगर आपने लॉक किया है, तो हो सकता है कि एकतरफ़ा कॉल को गलती से सिंक्रोनस कॉल में बदलने पर, आपको लॉक करने से जुड़ी समस्याएं आएं.
सिंगल बाइंडर थ्रेड और डेडलॉक
Binder का लेन-देन मॉडल, रीएंट्रेंसी की अनुमति देता है. इसलिए, अगर किसी प्रोसेस में एक ही बाइंडर थ्रेड है, तो भी आपको लॉकिंग की ज़रूरत होगी. उदाहरण के लिए, मान लें कि आपको सिंगल-थ्रेड वाली प्रोसेस A में किसी सूची को दोहराना है. सूची में मौजूद हर आइटम के लिए, आपको आउटगोइंग बाइंडर ट्रांज़ैक्शन करना होगा. अगर कॉल की जा रही फ़ंक्शन को लागू करने से, प्रोसेस A में होस्ट किए गए नोड के लिए नया बाइंडर लेन-देन होता है, तो उस लेन-देन को उसी थ्रेड पर हैंडल किया जाता है जो सूची को दोहरा रहा था. अगर उस लेन-देन को लागू करने से सूची में बदलाव होता है, तो बाद में सूची को दोहराते समय आपको समस्याएं आ सकती हैं.
थ्रेडपूल का साइज़ कॉन्फ़िगर करना
जब किसी सेवा के कई क्लाइंट होते हैं, तो थ्रेडपूल में ज़्यादा थ्रेड जोड़ने से, थ्रेड के लिए होने वाले टकराव को कम किया जा सकता है. साथ ही, एक साथ ज़्यादा कॉल किए जा सकते हैं. एक साथ कई अनुरोध मिलने की समस्या को ठीक करने के बाद, ज़्यादा थ्रेड जोड़ी जा सकती हैं. यह समस्या तब हो सकती है, जब ज़्यादा थ्रेड जोड़े जाएं. ऐसा हो सकता है कि कुछ थ्रेड का इस्तेमाल, कम लोड वाले वर्कलोड के दौरान न किया जाए.
थ्रेड, कॉन्फ़िगर किए गए ज़्यादा से ज़्यादा थ्रेड की संख्या तक, मांग पर स्पॉन होते हैं. बाइंडर थ्रेड स्पॉन होने के बाद, यह तब तक चालू रहता है, जब तक इसे होस्ट करने वाली प्रोसेस खत्म नहीं हो जाती.
libbinder लाइब्रेरी में डिफ़ॉल्ट रूप से 15 थ्रेड होते हैं. इस वैल्यू को बदलने के लिए, setThreadPoolMaxThreadCount
का इस्तेमाल करें:
using ::android::ProcessState;
ProcessState::self()->setThreadPoolMaxThreadCount(size_t maxThreads);