โมเดลการทำงานแบบมัลติเธรดของ Binder ออกแบบมาเพื่ออำนวยความสะดวกในการเรียกใช้ฟังก์ชันภายใน แม้ว่าการเรียกใช้เหล่านั้นอาจเป็นการเรียกใช้กระบวนการระยะไกลก็ตาม โดยเฉพาะอย่างยิ่ง กระบวนการใดก็ตามที่โฮสต์โหนดจะต้องมีพูลของ Binder Thread อย่างน้อย 1 รายการเพื่อจัดการธุรกรรมกับโหนดที่โฮสต์ในกระบวนการนั้น
ธุรกรรมแบบซิงโครนัสและอะซิงโครนัส
Binder รองรับธุรกรรมแบบซิงโครนัสและอะซิงโครนัส ส่วนต่อไปนี้จะอธิบายวิธีดำเนินการธุรกรรมแต่ละประเภท
ธุรกรรมแบบซิงโครนัส
ธุรกรรมแบบซิงโครนัสจะบล็อกจนกว่าจะดำเนินการในโหนดและผู้เรียกจะได้รับคำตอบสำหรับธุรกรรมนั้น รูปต่อไปนี้แสดงวิธีดำเนินการธุรกรรมแบบซิงโครนัส
รูปที่ 1 ธุรกรรมแบบซิงโครนัส
หากต้องการดำเนินการธุรกรรมแบบซิงโครนัส Binder จะทำดังนี้
- เธรดใน Threadpool เป้าหมาย (T2 และ T3) จะเรียกใช้ไดรเวอร์เคอร์เนลเพื่อรอรับงานที่เข้ามา
- เคอร์เนลได้รับธุรกรรมใหม่และปลุกเธรด (T2) ในกระบวนการเป้าหมายเพื่อจัดการธุรกรรม
- เธรดการเรียก (T1) จะบล็อกและรอคำตอบ
- กระบวนการเป้าหมายจะดำเนินการธุรกรรมและแสดงผลคำตอบ
- เธรดในกระบวนการเป้าหมาย (T2) จะเรียกใช้ไดรเวอร์เคอร์เนลอีกครั้งเพื่อรอรับงานใหม่
ธุรกรรมแบบอะซิงโครนัส
ธุรกรรมแบบอะซิงโครนัสจะไม่บล็อกจนกว่าจะเสร็จสมบูรณ์ เธรดการเรียกจะเลิกบล็อกทันทีที่ส่งธุรกรรมไปยังเคอร์เนล รูปต่อไปนี้แสดงวิธีดำเนินการธุรกรรมแบบอะซิงโครนัส
รูปที่ 2 ธุรกรรมแบบอะซิงโครนัส
- เธรดใน Threadpool เป้าหมาย (T2 และ T3) จะเรียกใช้ไดรเวอร์เคอร์เนลเพื่อรอรับงานที่เข้ามา
- เคอร์เนลได้รับธุรกรรมใหม่และปลุกเธรด (T2) ในกระบวนการเป้าหมายเพื่อจัดการธุรกรรม
- เธรดการเรียก (T1) จะดำเนินการต่อไป
- กระบวนการเป้าหมายจะดำเนินการธุรกรรมและแสดงผลคำตอบ
- เธรดในกระบวนการเป้าหมาย (T2) จะเรียกใช้ไดรเวอร์เคอร์เนลอีกครั้งเพื่อรอรับงานใหม่
ระบุฟังก์ชันแบบซิงโครนัสหรืออะซิงโครนัส
ฟังก์ชันที่ทำเครื่องหมายเป็น oneway ในไฟล์ AIDL จะเป็นแบบอะซิงโครนัส เช่น
oneway void someCall();
หากไม่ได้ทำเครื่องหมายฟังก์ชันเป็น oneway ฟังก์ชันนั้นจะเป็นแบบซิงโครนัส แม้ว่าฟังก์ชันจะแสดงผล void ก็ตาม
การซีเรียลไลซ์ธุรกรรมแบบอะซิงโครนัส
Binder จะซีเรียลไลซ์ธุรกรรมแบบอะซิงโครนัสจากโหนดเดียว รูปต่อไปนี้แสดงวิธีที่ Binder ซีเรียลไลซ์ธุรกรรมแบบอะซิงโครนัส
รูปที่ 3 การซีเรียลไลซ์ธุรกรรมแบบอะซิงโครนัส
- เธรดใน Threadpool เป้าหมาย (B1 และ B2) จะเรียกใช้ไดรเวอร์เคอร์เนลเพื่อรอรับงานที่เข้ามา
- ระบบจะส่งธุรกรรม 2 รายการ (T1 และ T2) ในโหนดเดียวกัน (N1) ไปยังเคอร์เนล
- เคอร์เนลได้รับธุรกรรมใหม่และซีเรียลไลซ์ธุรกรรมเหล่านั้นเนื่องจากมาจากโหนดเดียวกัน (N1)
- ระบบจะส่งธุรกรรมอื่นในโหนดอื่น (N2) ไปยังเคอร์เนล
- เคอร์เนลได้รับธุรกรรมที่ 3 และปลุกเธรด (B2) ในกระบวนการเป้าหมายเพื่อจัดการธุรกรรม
- กระบวนการเป้าหมายจะดำเนินการธุรกรรมแต่ละรายการและแสดงผลคำตอบ
ธุรกรรมแบบซ้อน
ธุรกรรมแบบซิงโครนัสสามารถซ้อนกันได้ โดยเธรดที่จัดการธุรกรรมสามารถออกธุรกรรมใหม่ได้ ธุรกรรมแบบซ้อนอาจเป็นกระบวนการอื่น หรือเป็นกระบวนการเดียวกันกับที่คุณได้รับธุรกรรมปัจจุบัน ลักษณะการทำงานนี้เลียนแบบการเรียกใช้ฟังก์ชันภายใน เช่น สมมติว่าคุณมีฟังก์ชันที่มีฟังก์ชันแบบซ้อนดังนี้
def outer_function(x):
def inner_function(y):
def inner_inner_function(z):
หากเป็นการเรียกใช้ภายใน ระบบจะดำเนินการในเธรดเดียวกัน
โดยเฉพาะอย่างยิ่ง หากผู้เรียก inner_function เป็นกระบวนการ
ที่โฮสต์โหนดที่ใช้ inner_inner_function การเรียกใช้
inner_inner_function จะดำเนินการในเธรดเดียวกัน
รูปต่อไปนี้แสดงวิธีที่ Binder จัดการธุรกรรมแบบซ้อน
รูปที่ 4 ธุรกรรมแบบซ้อน
- เธรด A1 ขอให้เรียกใช้
foo() - เธรด B1 จะเรียกใช้
bar()ซึ่ง A จะเรียกใช้ในเธรด A1 เดียวกัน ซึ่งเป็นส่วนหนึ่งของคำขอนี้
รูปต่อไปนี้แสดงการดำเนินการเธรดหากโหนดที่ใช้ bar() อยู่ในกระบวนการอื่น
รูปที่ 5 ธุรกรรมแบบซ้อนในกระบวนการต่างๆ
- เธรด A1 ขอให้เรียกใช้
foo() - เธรด B1 จะเรียกใช้
bar()ซึ่งจะเรียกใช้ในเธรด C1 อื่น ซึ่งเป็นส่วนหนึ่งของคำขอนี้
รูปต่อไปนี้แสดงวิธีที่เธรดใช้กระบวนการเดียวกันซ้ำในห่วงโซ่ธุรกรรม
รูปที่ 6 ธุรกรรมแบบซ้อนที่ใช้เธรดซ้ำ
- กระบวนการ A เรียกใช้กระบวนการ B
- กระบวนการ B เรียกใช้กระบวนการ C
- จากนั้นกระบวนการ C จะเรียกใช้กระบวนการ A อีกครั้ง และเคอร์เนลจะใช้เธรด A1 ในกระบวนการ A ซ้ำ ซึ่งเป็นส่วนหนึ่งของห่วงโซ่ธุรกรรม
สำหรับธุรกรรมแบบอะซิงโครนัส การซ้อนจะไม่มีบทบาทใดๆ เนื่องจากไคลเอ็นต์จะไม่รอผลลัพธ์ของธุรกรรมแบบอะซิงโครนัส จึงไม่มีการซ้อน หากแฮนเดิลเลอร์ของธุรกรรมแบบอะซิงโครนัสเรียกใช้กระบวนการที่ออกธุรกรรมแบบอะซิงโครนัส ธุรกรรมนั้นจะจัดการได้ในเธรดว่างใดก็ได้ในกระบวนการนั้น
หลีกเลี่ยงการล็อกตาย
รูปต่อไปนี้แสดงการติดตายที่พบได้ทั่วไป
รูปที่ 7 การติดตายที่พบได้ทั่วไป
- กระบวนการ A ใช้ Mutex MA และเรียกใช้ Binder (T1) ไปยังกระบวนการ B ซึ่งพยายามใช้ Mutex MB ด้วย
- ในขณะเดียวกัน กระบวนการ B ใช้ Mutex MB และเรียกใช้ Binder (T2) ไปยังกระบวนการ A ซึ่งพยายามใช้ Mutex MA
หากธุรกรรมเหล่านี้ทับซ้อนกัน ธุรกรรมแต่ละรายการอาจใช้ Mutex ในกระบวนการของตนเองขณะรอให้กระบวนการอื่นปล่อย Mutex ซึ่งจะส่งผลให้เกิดการติดตาย
หากต้องการหลีกเลี่ยงการล็อกตายขณะใช้ Binder โปรดอย่าล็อกไว้เมื่อเรียกใช้ Binder
กฎการจัดลำดับการล็อกและการล็อกตาย
ภายในสภาพแวดล้อมการดำเนินการเดียว มักจะหลีกเลี่ยงการติดตายด้วยกฎการจัดลำดับการล็อก อย่างไรก็ตาม เมื่อทำการเรียกใช้ระหว่างกระบวนการและระหว่างฐานโค้ด โดยเฉพาะอย่างยิ่งเมื่อมีการอัปเดตโค้ด การรักษากฎการจัดลำดับและการประสานงานจึงเป็นไปไม่ได้
Mutex เดียวและการล็อกตาย
ด้วยธุรกรรมแบบซ้อน กระบวนการ B สามารถเรียกใช้เธรดเดียวกันในกระบวนการ A ที่ใช้ Mutex ได้โดยตรง ดังนั้น การเรียกซ้ำที่ไม่คาดคิดอาจทำให้เกิดการติดตายด้วย Mutex เดียว
การเรียกใช้แบบอะซิงโครนัสและการล็อกตาย
แม้ว่าการเรียกใช้ Binder แบบอะซิงโครนัสจะไม่บล็อกจนกว่าจะเสร็จสมบูรณ์ แต่คุณก็ควรหลีกเลี่ยงการล็อกไว้สำหรับการเรียกใช้แบบอะซิงโครนัสด้วย หากคุณล็อกไว้ คุณอาจพบปัญหาการล็อกหากมีการเปลี่ยนการเรียกใช้แบบทางเดียวเป็นการเรียกใช้แบบซิงโครนัสโดยไม่ได้ตั้งใจ
Binder Thread เดียวและการล็อกตาย
โมเดลธุรกรรมของ Binder อนุญาตให้มีการเรียกซ้ำ ดังนั้นแม้ว่ากระบวนการจะมี Binder Thread เดียว คุณก็ยังต้องล็อก เช่น สมมติว่าคุณกำลังวนซ้ำรายการในกระบวนการ A แบบเธรดเดียว คุณจะทำธุรกรรม Binder ขาออกสำหรับแต่ละรายการในรายการ หากการติดตั้งใช้งานฟังก์ชันที่คุณเรียกใช้ทำธุรกรรม Binder ใหม่ไปยังโหนดที่โฮสต์ในกระบวนการ A ระบบจะจัดการธุรกรรมนั้นในเธรดเดียวกันกับที่วนซ้ำรายการ หากการติดตั้งใช้งานธุรกรรมนั้นแก้ไขรายการเดียวกัน คุณอาจพบปัญหาเมื่อวนซ้ำรายการต่อในภายหลัง
กำหนดค่าขนาด Threadpool
เมื่อบริการมีไคลเอ็นต์หลายราย การเพิ่มเธรดลงใน Threadpool จะช่วยลดการแย่งชิงและให้บริการการเรียกใช้ได้มากขึ้นแบบขนาน หลังจากจัดการการทำงานพร้อมกันอย่างถูกต้องแล้ว คุณจะเพิ่มเธรดได้ ปัญหาที่อาจเกิดขึ้นจากการเพิ่มเธรดคือเธรดบางรายการอาจไม่ได้ใช้ในระหว่างปริมาณงานที่น้อย
ระบบจะสร้างเธรดตามความต้องการจนกว่าจะถึงค่าสูงสุดที่กำหนดค่าไว้ หลังจากสร้าง Binder Thread แล้ว เธรดจะทำงานอยู่จนกว่ากระบวนการที่โฮสต์เธรดจะสิ้นสุด
ไลบรารี libbinder มีเธรดเริ่มต้น 15 รายการ ใช้ setThreadPoolMaxThreadCount เพื่อเปลี่ยนค่านี้
using ::android::ProcessState;
ProcessState::self()->setThreadPoolMaxThreadCount(size_t maxThreads);