مسئله تولیدکننده-مصرف‌کننده
استفاده از پرچم‌ها (سمافر)

سمافرها مشکل از دست رفتن فراخوانی بیدار کردن را حل می‌کنند. در راه حل زیر، ما از دو سمافور با نام‌های fillcount و emptycount برای حل مسئله استفاده می‌کنیم. fillcount تعداد آیتم‌هایی است که هم‌اکنون در بافر وجود دارد و قابل خواندن است، در حالیکه emptycount تعداد فضاهای خالی موجود در بافر است که می‌توان آیتم‌ها را در آن نوشت. زمانیکه یک آیتم جدید در داخل بافر قرار داده می‌شود، fillcount افزایش پیدا می‌کند و emptycount کاهش پیدا می‌کند. زمانیکه تولیدکننده سعی می‌کند تا emptycount را، که مقدار صفر دارد، کاهش دهد به خواب می‌شود. بعداً، وقتی که یک آیتم مصرف می‌شود، emptycount افزایش پیدا می‌کند و تولیدکننده بیدار می‌شود. طرز کار مصرف‌کننده نیز به همین شکل است.
semaphore fillCount = 0; // items produced
semaphore emptyCount = BUFFER_SIZE; // remaining space

procedure producer()
{
 while (true)
 {
 item = produceItem();
 down(emptyCount);
 putItemIntoBuffer(item);
 up(fillCount);
 }
}

procedure consumer()
{
 while (true)
 {
 down(fillCount);
 item = removeItemFromBuffer();
 up(emptyCount);
 consumeItem(item);
 }
}
راه حل بالا فقط زمانی خوب جواب می‌دهد که فقط یک تولیدکننده و یک مصرف‌کننده داشته باشیم. زمانی که چندین تولیدکننده داشته باشیم که فضای حافظه یکسانی را در اشتراک داشته باشند، یا چندین مصرف‌کننده داشته باشیم که فضای حافظه یکسانی در اشتراک داشته باشند، این راه حل دارای شرایط رقابتی جدی است که می‌تواند منجر به این شود که دو یا بیش از دو پروسه به‌طور همزمان در یک درگاه (slot) بنویسند یا بخوانند. برای اینکه بفهمیم چگونه این حالت امکان‌پذیر است، تصور کنید که چگونه تابع putItemIntoBuffer() قابل پیاده‌سازی است. این تابع می‌تواند حاوی دو عمل باشد، یکی برای تعیین درگاه موجود بعدی و دیگری برای نوشتن در داخل آن. اگر تابع را بتوان به‌طور همزمان توسط چندین تولیدکننده اجرا کرد آنگاه سناریوی زیر محتمل است:
دو تولیدکننده emptycount را کاهش می‌دهند.
یکی از تولیدکننده‌ها درگاه خالی بعدی را در بافر مشخص می‌کند.
دومین تولیدکننده درگاه خالی بعدی را مشخص می‌کند و نتایج مشابهی با تولیدکننده اول به دست می‌آورد.
هر دو تولیدکننده به داخل درگاه یکسانی می‌نویسند.
برای غلبه بر این مشکل، باید این اطمینان را پیدا کنیم که فقط یک تولیدکننده در هر لحظه تابع putItemIntoBuffer() را اجرا می‌کند. به بیان دیگر، ما نیاز به روشی برای اجرای یک ناحیه بحرانی با انحصار متقابل داریم. راه حل برای چندین تولیدکننده و چندین مصرف‌کننده در زیر نشان داده شده‌است:
mutex buffer_mutex; // similar to "semaphore buffer_mutex = 1", but different (see notes below)
semaphore fillCount = 0;
semaphore emptyCount = BUFFER_SIZE;

procedure producer()
{
 while (true)
 {
 item = produceItem();
 down(emptyCount);
 down(buffer_mutex);
 putItemIntoBuffer(item);
 up(buffer_mutex);
 up(fillCount);
 }
}

procedure consumer()
{
 while (true)
 {
 down(fillCount);
 down(buffer_mutex);
 item = removeItemFromBuffer();
 up(buffer_mutex);
 up(emptyCount);
 consumeItem(item);
 }
}
توجه کنید که ترتیب افزایش یا کاهش سمافورهای مختلف مهم است: تغییر این ترتیب ممکن است منجر به یک بن‌بست شود. در اینجا، مهم است که بدانیم، گرچه به نظر می‌رسد که mutex به شکل یک سمافور با مقدار ۱ عمل می‌کند (سمافور باینری)، اما تفاوت در این است که mutex دارای ماهیت مالکیت است. مالکیت به این معنی است که mutex را می‌توان فقط توسط همان پروسه ای که آن را به مقدار صفر کاهش داده‌است، به مقدار یک افزایش داد، و تمام عمل‌های دیگر منتظر می‌مانند تا زمانیکه میوتکس برای کاهش یافتن موجود شود (در واقع به این معنی است که منبع موجود است)، درنتیجه، قطعاً انحصار متقابل بوجود می‌آید و از بن‌بست پیشگیری می‌شود. در نتیجه استفادهٔ نادرست از mutexها می‌تواند، در زمانیکه دسترسی انحصاری لازم نیست، باعث متوقف شدن بسیاری از پروسه‌ها شود، اما mutex به جای سمافور استفاده می‌شود.