مدیریت رشته‌ها

مدل رشته‌بندی Binder به‌گونه‌ای طراحی شده است که تماس‌های تابع محلی را تسهیل کند، حتی اگر این تماس‌ها مربوط به فرایندی از دور باشند. به‌طور مشخص، هر فرایندی که میزبان گره است باید مجموعه‌ای از یک یا چند رشته binder داشته باشد تا تراکنش‌های گره‌های میزبانی‌شده در آن فرایند را مدیریت کند.

تراکنش‌های هم‌زمان و غیرهم‌زمان

‫Binder از تراکنش‌های هم‌زمان و ناهم‌زمان پشتیبانی می‌کند. بخش‌های زیر توضیح می‌دهند که هر نوع تراکنش چگونه اجرا می‌شود.

تراکنش‌های هم‌زمان

تراکنش‌های هم‌زمان تا زمانی که در گره اجرا شوند و پاسخ آن تراکنش توسط تماس‌گیرنده دریافت شود، مسدود می‌شوند. شکل زیر نحوه اجرای تراکنش همگام را نشان می‌دهد:

تراکنش هم‌زمان.

شکل ۱. تراکنش هم‌زمان.

برای اجرای تراکنش همگام، binder کارهای زیر را انجام می‌دهد:

  1. رشته‌های موجود در استخر رشته هدف (T2 و T3) برای انتظار کشیدن برای کار ورودی، درایور هسته را فرا می‌خوانند.
  2. هسته تراکنش جدیدی دریافت می‌کند و رشته‌ای (T2) را در فرایند مقصد برای مدیریت تراکنش بیدار می‌کند.
  3. رشته تماس (T1) مسدود می‌شود و منتظر پاسخ می‌ماند.
  4. فرایند هدف تراکنش را اجرا می‌کند و پاسخی برمی‌گرداند.
  5. رشته در فرایند هدف (T2) به درایور هسته برمی‌گردد تا منتظر کار جدید بماند.

تراکنش‌های غیرهمگام

تراکنش‌های ناهمگام برای تکمیل مسدود نمی‌شوند؛ رشته فراخوانی به‌محض اینکه تراکنش به هسته منتقل شد، رفع انسداد می‌شود. شکل زیر نحوه اجرای تراکنش ناهم‌زمان را نشان می‌دهد:

تراکنش غیرهمگام.

شکل ۲. تراکنش غیرهمگام.

  1. رشته‌های موجود در استخر رشته هدف (T2 و T3) برای انتظار برای کار ورودی، درایور هسته را فراخوانی می‌کنند.
  2. هسته تراکنش جدیدی دریافت می‌کند و رشته‌ای (T2) را در فرایند هدف برای رسیدگی به تراکنش بیدار می‌کند.
  3. رشته فراخوان (T1) اجرا را ادامه می‌دهد.
  4. فرایند هدف تراکنش را اجرا می‌کند و پاسخی برمی‌گرداند.
  5. رشته در فرایند هدف (T2) برای انتظار برای کار جدید، دوباره به درایور هسته فراخوانی می‌کند.

شناسایی تابع هم‌زمان یا ناهم‌زمان

توابعی که در فایل AIDL با oneway علامت‌گذاری شده‌اند ناهم‌زمان هستند. برای مثال:

oneway void someCall();

اگر تابعی به‌عنوان oneway علامت‌گذاری نشده باشد، تابع همگام است، حتی اگر تابع void را برگرداند.

سریال‌سازی تراکنش‌های ناهمگام

Binder تراکنش‌های ناهمگام را از هر گره واحدی سریال‌سازی می‌کند. شکل زیر نحوه سریال‌سازی تراکنش‌های ناهم‌زمان توسط بایندر را نشان می‌دهد:

سری‌سازی تراکنش‌های ناهم‌زمان.

شکل ۳. سریال‌سازی تراکنش‌های ناهمگام.

  1. رشته‌های موجود در استخر رشته هدف (B1 و B2) برای انتظار کشیدن برای کار ورودی، درایور هسته را فراخوانی می‌کنند.
  2. دو تراکنش (T1 و T2) در گره یکسان (N1) به هسته ارسال می‌شوند.
  3. هسته تراکنش‌های جدیدی دریافت می‌کند و چون از همان گره (N1) هستند، آن‌ها را سریال‌سازی می‌کند.
  4. تراکنش دیگری در گره‌ای متفاوت (N2) به هسته ارسال می‌شود.
  5. هسته تراکنش سوم را دریافت می‌کند و رشته‌ای (B2) را در فرایند هدف برای مدیریت تراکنش بیدار می‌کند.
  6. فرایندهای هدف هر تراکنش را اجرا می‌کند و پاسخی برمی‌گرداند.

تراکنش‌های تودرتو

تراکنش‌های هم‌زمان می‌توانند تودرتو باشند؛ رشته‌ای که تراکنشی را مدیریت می‌کند می‌تواند تراکنش جدیدی صادر کند. تراکنش تودرتو می‌تواند به فرایند دیگری باشد، یا به همان فرایندی که تراکنش فعلی را از آن دریافت کرده‌اید. این رفتار از فراخوانی‌های تابع محلی تقلید می‌کند. برای مثال، فرض کنید تابعی با توابع تودرتو دارید:

def outer_function(x):
    def inner_function(y):
        def inner_inner_function(z):

اگر این تماس‌ها محلی باشند، در همان رشته اجرا می‌شوند. به‌طور خاص، اگر فراخواننده inner_function همان فرایند میزبان گره‌ای باشد که inner_inner_function را پیاده‌سازی می‌کند، فراخوانی inner_inner_function در همان رشته اجرا می‌شود.

شکل زیر نشان می‌دهد که binder چگونه تراکنش‌های تودرتو را مدیریت می‌کند:

تراکنش‌های تودرتو.

شکل ۴. تراکنش‌های تودرتو.

  1. درخواست‌های رشته A1 درحال اجرای foo() است.
  2. به‌عنوان بخشی از این درخواست، رشته B1 bar() را اجرا می‌کند که A در همان رشته A1 اجرا می‌شود.

شکل زیر اجرای رشته را درصورتی‌که گره‌ای که bar() را پیاده‌سازی می‌کند در فرایندی متفاوت باشد نشان می‌دهد:

تراکنش‌های تودرتو در فرایندهای مختلف.

شکل ۵. تراکنش‌های تودرتو در فرایندهای مختلف.

  1. درخواست‌های رشته A1 درحال اجرای foo() است.
  2. به‌عنوان بخشی از این درخواست، رشته B1 اجرا می‌شود bar() که در رشته C1 دیگری اجرا می‌شود.

شکل زیر نشان می‌دهد که چگونه رشته در هر جایی از زنجیره تراکنش از همان فرایند استفاده مجدد می‌کند:

تراکنش‌های تودرتو که از رشته‌ای استفاده مجدد می‌کنند.

شکل ۶. تراکنش‌های تودرتو که از رشته‌ای مجدداً استفاده می‌کنند.

  1. فرایند A با فرایند B تماس می‌گیرد.
  2. فرایند «ب» با فرایند «پ» تماس می‌گیرد.
  3. سپس فرایند C با فرایند A تماس می‌گیرد و هسته از رشته A1 در فرایند A که بخشی از زنجیره تراکنش است استفاده مجدد می‌کند.

برای تراکنش‌های ناهم‌زمان، تودرتو بودن نقشی ندارد؛ مشتری منتظر نتیجه تراکنش ناهم‌زمان نمی‌ماند، بنابراین تودرتو بودن وجود ندارد. اگر کارپرداز تراکنش ناهم‌زمان با فرایندی که آن تراکنش ناهم‌زمان را صادر کرده است تماس بگیرد، آن تراکنش می‌تواند در هر رشته آزاد در آن فرایند پردازش شود.

اجتناب از بن‌بست‌ها

تصویر زیر یک بن‌بست رایج را نشان می‌دهد:

بن‌بست رایج.

شکل ۷. بن‌بست رایج.

  1. فرایند A قفل انحصاری MA را می‌گیرد و یک تماس چسباننده (T1) به فرایند B برقرار می‌کند که آن هم تلاش می‌کند قفل انحصاری MB را بگیرد.
  2. هم‌زمان، فرایند B قفل MB را می‌گیرد و فراخوانی Binder (T2) را به فرایند A انجام می‌دهد که تلاش می‌کند قفل MA را بگیرد.

اگر این تراکنش‌ها هم‌پوشانی داشته باشند، هر تراکنش می‌تواند به‌طور بالقوه در فرایند خود یک انحصار بگیرد و منتظر بماند تا فرایند دیگر انحصار را آزاد کند، که منجر به بن‌بست می‌شود.

برای جلوگیری از بن‌بست هنگام استفاده از binder، هنگام انجام تماس binder هیچ قفلی را نگه ندارید.

قفل کردن قوانین ترتیب و بن‌بست‌ها

در یک محیط اجرای واحد، معمولاً با قانون ترتیب قفل از بن‌بست اجتناب می‌شود. بااین‌حال، هنگام برقراری تماس بین فرایندها و بین پایه‌های کد، به‌ویژه با به‌روزرسانی کد، حفظ و هماهنگی قانون ترتیب غیرممکن است.

تک‌قفل متقابل و بن‌بست‌ها

با تراکنش‌های تودرتو، فرایند B می‌تواند مستقیماً به همان رشته در فرایند A که یک متقابل را نگه می‌دارد برگردد. بنابراین، به‌دلیل بازگشت غیرمنتظره، همچنان ممکن است با یک تک‌قفل به بن‌بست برسید.

تماس‌های غیرهمگام و بن‌بست‌ها

درحالی‌که فراخوانی‌های Binder ناهمزمان برای تکمیل مسدود نمی‌شوند، شما باید از نگه داشتن قفل برای تماس‌های ناهمزمان نیز اجتناب کنید. اگر قفلی را نگه دارید، ممکن است درصورتی‌که تماس یک‌طرفه به‌طور تصادفی به تماس هم‌زمان تغییر کند، با مشکلات قفل مواجه شوید.

رشته تک‌پیوند و بن‌بست‌ها

مدل تراکنش Binder امکان ورود مجدد را فراهم می‌کند، بنابراین حتی اگر فرایندی یک رشته binder داشته باشد، همچنان به قفل کردن نیاز دارید. برای مثال، فرض کنید درحال تکرار کردن فهرستی در فرایند تک‌رشته‌ای A هستید. برای هر مورد در فهرست، یک تراکنش بسته‌بندی خروجی انجام می‌دهید. اگر پیاده‌سازی تابعی که فراخوانی می‌کنید تراکنش پیونددهنده جدیدی را به گره میزبانی‌شده در فرایند A ایجاد کند، آن تراکنش در همان رشته‌ای که فهرست را تکرار می‌کرد مدیریت می‌شود. اگر پیاده‌سازی آن تراکنش فهرست یکسانی را تغییر دهد، ممکن است وقتی بعداً به تکرار کردن فهرست ادامه می‌دهید با مشکلاتی مواجه شوید.

پیکربندی اندازه استخر رشته

وقتی سرویسی چندین کارخواه دارد، افزودن رشته‌های بیشتر به استخر رشته می‌تواند رقابت را کاهش دهد و تماس‌های بیشتری را به‌صورت موازی ارائه دهد. پس‌از اینکه به‌درستی با هم‌زمان‌گرایی برخورد کردید، می‌توانید رشته‌های بیشتری اضافه کنید. مشکلی که می‌تواند ناشی از افزودن رشته‌های بیشتر باشد که ممکن است برخی‌از رشته‌ها درطول حجم کاری کم استفاده نشوند.

رشته‌ها درصورت نیاز تا حداکثر پیکربندی‌شده ایجاد می‌شوند. پس‌از ایجاد رشته binder، این رشته تا زمانی که فرایند میزبان آن به‌پایان برسد فعال می‌ماند.

کتابخانه libbinder به‌طور پیش‌فرض ۱۵ رشته دارد. برای تغییر این مقدار، از setThreadPoolMaxThreadCount استفاده کنید:

using ::android::ProcessState;
ProcessState::self()->setThreadPoolMaxThreadCount(size_t maxThreads);