مسئله اول: اگر چند کاربر همزمان به یک صفحه PHP (یا صفحات مختلف، فرقی نمیکنه!) درخواست ارسال کنند، آیا PHP به صورت موازی (و همزمان) به درخواست‌ها پاسخ می‌ده؟

مسئله دوم: اگر یک کاربر همزمان چند درخواست (مثلاً به وسیله Ajax) به صفحات جداگانه PHP از یک دامین ارسال کند، آیا PHP به صورت موازی به درخواست‌ها پاسخ می‌ده؟

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

از منظر معماری، وب سرورها بر مبنای دو رهیافت عمده پیاده‌سازی می‌شوند

  1. رهیافت موازی (concurrent)
  2. رهیافت تک پردازه‌ای رویداد محور (event driven)

رهیافت موازی به وب سرور اجازه می‌دهد تا چندین درخواست از طرف کاربران را به صورت همزمان هندل کند. بر مبنای این رهیافت وب سرورها به صورت چند پردازه‌ای (Multiprocess)، چندنخی (Multithread) و یا ترکیبی (Hybrid) از این دو پیاده سازی می‌شوند. در حالت چند پردازه‌ای یک پردازه پدر وجود دارد که تعدادی پردازه فرزند را اجرا می‌کند. در این حالت هر پردازه فرزند مسئول پاسخ گویی به یک درخواست است. در حالت چند نخی هر نخ مسئول پاسخ گویی به یک درخواست از طرف کاربر است. در اغلب وب سرورها از معماری ترکیبی استفاده می‌شود که در آن هر نخ مسئول رسیدگی به یک اتصال است. از این دست وب سرورهای بسیاری طراحی شده است. برای نمونه سرویس دهنده وب Apache HTTP Server به صورت چند پردازه‌ای پیاده سازی شده است.

سرویس دهنده وب چند پردازه ای

سرویس دهنده وب چند نخی

در مقابل سرویس دهنده های وب همزمان مسدود کننده I/O رهیافت رویداد محور نیز معماری دیگری برای پیاده سازی سرویس دهنده های وب است. به خاطر رویکرد ناهمگام/غیر مسدود کننده این دست از سرویس دهنده های وب دیگر نیازی به مدل ایجاد نخ برای هر اتصال وجود ندارد. در این معماری یک نخ برای چندین اتصال همزمان در نظر گرفته می شود. در هر نخ یک حلقه رویداد (event loop) تعبیه شده و درخواست های جدید به صورت یک رویداد در صف رویداد (event queue) قرار می گیرد و به نوبت در حلقه رویداد پردازش خواهد شد. وب سرورهایی نظیر NGINX، Lighttpd و Tornado مبتنی بر معماری رویداد محور طراحی شده اند.

سرویس دهنده وب رویداد محور

بحث تو این زمینه یه مقدار مفصله و برای اطلاعات بیشتر میتونید به اینجا مراجعه کنید.

پاسخ مسئله اول

در مورد مسئله اول سرویس دهندگان وب امروزی می توانند چندین درخواست را به صورت موازی پردازش نمایند.  اساساً پردازش موازی درخواست ها بر عهده PHP یا هر زبان سمت سرور دیگری نیست؛ چرا که وب سرور ها قادرند به صورت موازی یک اسکریپت را در مثلاً پردازه های جدا اجرا کنند. بنابراین بدون نگرانی پردازش موازی چند درخواست همزمان به یک صفحه از کاربران متفاوت را بر عهده سرویس دهنده وب می گذاریم.

پاسخ مسئله دوم

آنچه که در پاسخ مسئله اول بیان شد در مورد مسئله دوم نیز بر قرار است. وب سرورها قادرند چند درخواست همزمان از یک کاربر را به صفحات مجزا به صورت موازی پردازش کنند. اما با تعجب باید گفت درخواست های ارسال شده به برخی صفحات مجزای PHP از طرف یک کاربر به صورت پشت سر هم (و نه موازی) پاسخ داده می شود. در حقیقت تا زمانی که به درخواستی پاسخ داده نشود درخواست های بعدی در صف مانده و پاسخ داده نمی شوند. مشکل کجاست؟! آیا سرویس دهندگان وب درخواست های یک کاربر (یک اتصال) را به صورت سریال پردازش می کنند؟

در مورد مسئله دوم مشکل سرویس دهنده وب نیست؛ بلکه مشکل زبان PHP است! به صورت پیش فرض PHP داده های session را در فایل ذخیره می کند. زمانی که درخواستی به یک اسکریپت PHP که یک session را شروع می کند (session_start)، فرستاده شود، این فایل session قفل می شود.

بنابراین زمانی که تعدادی درخواست همزمان از طرف یک کاربر به (حتی) اسکریپت های مختلف PHP فرستاده می شود اولین درخواست، فایلِ session را قفل کرده و جلوی کامل شدن درخواست های بعدی را می گیرد. به بیان دیگر درخواست های بعدی از زمان فراخوانی session_start اولین درخواست معلق می مانند تا فایل session از حالت قفل خارج شود. این رویداد در مورد استفاده از فریم ورکی نظیر CakePHP که برای هر درخواستی seesion_start را صدا می زند، همیشه روی می دهد.

خوب راه حل این مشکل چیست؟ فایل session قفل باقی می ماند تا زمانی که اجرای اسکریپت کامل یا session به صورت دستی بسته شود. بنابراین برای این که اجرای درخواست های بعدی کمتر به تعویق بیافتد زمانی که نیازی به نوشتن در session نیاز ندارید، آن را به صورت دستی ببندید. برای بستن session می توانید از تابع زیر استفاده کنید:

session_write_close();

روش استفاده از این تابع در کد زیر نشان داده شده است (قطعه کد از اینجا):

<?php

// start the session
session_start();

// I can read/write to session
$_SESSION['latestRequestTime'] = time();

// close the session
session_write_close();

// now do my long-running code.
// still able to read from session, but not write
$twitterId = $_SESSION['twitterId'];

// dang Twitter can be slow, good thing my other Ajax calls
// aren't waiting for this to complete
$twitterFeed = fetchTwitterFeed($twitterId);

echo json_encode($twitterFeed);