رشته ها را دسته بزنید

مدل نخ‌کشی 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 برگرداند، یک تابع همگام (synchronous) است.

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

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

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

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

  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 اجرا می‌شود.

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

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

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

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

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

از بن‌بست‌ها اجتناب کنید

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

بن‌بست مشترک.

شکل ۷. بن‌بست مشترک.

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

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

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

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

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

تک‌متکس و بن‌بست‌ها

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

فراخوانی‌های ناهمزمان و بن‌بست‌ها

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

نخ اتصال دهنده تکی و بن بست ها

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

پیکربندی اندازه threadpool

وقتی یک سرویس چندین کلاینت دارد، اضافه کردن نخ‌های بیشتر به threadpool می‌تواند باعث کاهش تداخل و سرویس‌دهی موازی به فراخوانی‌های بیشتر شود. پس از اینکه به درستی با همزمانی برخورد کردید، می‌توانید نخ‌های بیشتری اضافه کنید. مشکلی که می‌تواند با اضافه کردن نخ‌های بیشتر ایجاد شود این است که برخی از نخ‌ها ممکن است در بارهای کاری بی‌صدا استفاده نشوند.

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

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

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