Semaphore: چیست، اصل کار و کاربرد در همگام‌سازی نخ‌ها

نویسنده: IT Sectr منتشر شده: 2026-03-19 زمان مطالعه: 8 دقیقه

Semaphore یک اولیه همگام‌سازی است که دسترسی به منبع مشترک را از طریق شمارنده و صف نخ‌های منتظر کنترل می‌کند. بر اساس ویکی‌پدیا، 2024، semaphore توسط ادسخر دیکسترا در سال 1965 برای حل مسائل تعامل چندنخی پیشنهاد شد. این ابزار به شما امکان می‌دهد تعداد نخ‌هایی را که همزمان با بخش بحرانی کار می‌کنند محدود کنید.

نکات اصلی

  • Semaphore — یک اولیه همگام‌سازی است که دسترسی را از طریق شمارنده مجوزها کنترل می‌کند.
  • Semaphore دودویی مقادیر 0 و 1 را می‌گیرد و مانند پرچم قفل عمل می‌کند.
  • Semaphore شمارشی امکان دسترسی همزمان تعداد مشخصی نخ را فراهم می‌کند.
  • برخلاف mutex، semaphore به نخ مالک وابسته نیست.
  • Deadlock — یکی از خطرات اصلی در استفاده نادرست از semaphore‌ها.

Semaphore چیست؟

Semaphore — یک اولیه همگام‌سازی است که از شمارنده برای کنترل دسترسی به منبع مشترک استفاده می‌کند. این مفهوم توسط ادسخر دیکسترا در سال 1965 ارائه شد و پایه‌ای برای تمام مکانیزم‌های همگام‌سازی مدرن در سیستم‌های عامل شد.

تعریف و هدف

Semaphore یک متغیر صحیح با دو عملیات اتمی است: wait (acquire) و signal (release). عملیات wait شمارنده را کاهش می‌دهد و signal آن را افزایش می‌دهد. وقتی شمارنده به صفر می‌رسد، نخی که wait را فراخوانی کرده تا زمانی که signal توسط نخ دیگری اجرا شود مسدود می‌شود.

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

تاریخچه و مبنای نظری

مفهوم semaphore در زمینه سیستم عامل THE که در دانشگاه فناوری آیندهوون توسعه یافته بود پدید آمد. دیکسترا semaphore را به عنوان یک انتزاع ریاضی رسمی کرد و ثابت کرد که برای پیاده‌سازی هر اولیه همگام‌سازی کافی است.

Semaphore چگونه کار می‌کند؟

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

شمارنده و عملیات اتمی

هنگام ایجاد semaphore، مقدار اولیه شمارنده مجوزها تعیین می‌شود. هر فراخوانی acquire شمارنده را 1 کاهش می‌دهد. اگر پس از آن شمارنده منفی شود، نخ مسدود می‌شود. عملیات release شمارنده را افزایش می‌دهد و یکی از نخ‌های منتظر را بیدار می‌کند.

kotlin
import java.util.concurrent.Semaphore

val semaphore = Semaphore(3)

fun accessResource() {
    semaphore.acquire()
    try {
        println("${Thread.currentThread().name} کار می‌کند")
    } finally {
        semaphore.release()
    }
}

صف انتظار و زمان‌بندی

وقتی نخ با شمارنده صفر acquire را فراخوانی می‌کند، سیستم عامل آن را در صف FIFO semaphore قرار می‌دهد. نخ به حالت BLOCKED می‌رود و زمان پردازنده مصرف نمی‌کند. پس از فراخوانی release، اولین نخ در صف به حالت RUNNABLE می‌رود و به منبع دسترسی پیدا می‌کند.

انواع semaphore

در نظریه همگام‌سازی دو نوع اصلی semaphore وجود دارد: دودویی (binary) و شمارشی (counting). انتخاب نوع به وظیفه خاص مدیریت دسترسی به منابع بستگی دارد.

Semaphore دودویی (Binary Semaphore)

Semaphore دودویی فقط مقادیر 0 و 1 را می‌گیرد. از نظر رفتار شبیه mutex است، اما بدون نیاز به مالکیت — هر نخی می‌تواند release را اجرا کند. چنین semaphore‌هایی برای پیاده‌سازی پرچم‌های آمادگی و رویدادهای بین نخی مناسب هستند.

kotlin
val ready = Semaphore(0)

fun producer() {
    Thread.sleep(1000)
    ready.release()
}

fun consumer() {
    ready.acquire()
    println("داده‌ها آماده")
}

Semaphore شمارشی (Counting Semaphore)

Semaphore شمارشی می‌تواند هر مقدار غیرمنفی را بگیرد. برای مدیریت استخری از منابع همگن که چندین نمونه در دسترس دارند استفاده می‌شود. مثلاً استخر 5 اتصال شبکه: هر acquire یک اتصال را می‌گیرد، release آن را به استخر بازمی‌گرداند.

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

پارامترSemaphore دودوییSemaphore شمارشی
محدوده0 یا 1از 0 تا N
نخ‌های همزمان1تا N
کاربردسیگنال‌دهی، پرچم‌هااستخر منابع، محدودسازی نرخ

Semaphore در مقابل Mutex

توسعه‌دهندگان اغلب semaphore را با mutex اشتباه می‌گیرند، اگرچه تفاوت‌های اساسی بین آنها وجود دارد. درک این تفاوت‌ها برای انتخاب مکانیزم همگام‌سازی صحیح در پروژه حیاتی است.

اصل مالکیت

تفاوت کلیدی — مفهوم مالکیت. Mutex همیشه می‌داند کدام نخ آن را گرفته است و فقط آن نخ می‌تواند آن را آزاد کند. Semaphore مالک ندارد: هر نخی می‌تواند release را فراخوانی کند، حتی بدون فراخوانی acquire. این باعث می‌شود mutex برای محافظت از داده‌ها امن‌تر و semaphore برای هماهنگی انعطاف‌پذیرتر باشد.

عملکرد و سناریوهای استفاده

در عمل، mutex برای قفل متقابل ساده به دلیل بهینه‌سازی‌های سناریوی معمولی سریع‌تر است. Semaphore به سربار اضافی برای نگهداری شمارنده نیاز دارد. با این حال، برای محدود کردن موازی‌سازی یا پیاده‌سازی الگوی «تولیدکننده-مصرف‌کننده»، semaphore ضروری است.

ویژگیSemaphoreMutex
مالکیتبدون مالکدارای مالک
آزادسازیهر نخیفقط نخ مالک
شمارندهاز 0 تا Nدودویی
کاربردمحدود کردن موازی‌سازی و سیگنال‌دهیمحافظت از بخش بحرانی
بازگشت‌پذیریخیربله (reentrant)

کاربرد Semaphore در توسعه موبایل

در توسعه برنامه‌های موبایل، Semaphore برای کنترل دسترسی به منابع محدود استفاده می‌شود: اتصالات شبکه، فایل‌ها، پایگاه‌های داده و اجزای سخت‌افزاری. پلتفرم‌های مدرن پیاده‌سازی‌های داخلی مناسبی ارائه می‌دهند.

استخر اتصالات به سرور

یکی از موارد معمول — استخر اتصالات HTTP. برنامه می‌تواند همزمان بیش از 4 درخواست به سرور ارسال نکند، زیرا API ارائه‌دهنده موازی‌سازی را محدود می‌کند. Semaphore با مقدار اولیه 4 تضمین می‌کند که در هر باری تعداد درخواست‌های همزمان از حد تجاوز نکند و نخ‌های باقی‌مانده در صف منتظر بمانند.

بدون semaphore، با افزایش ناگهانی فعالیت کاربر، زیرساخت سرور ممکن است دچار اضافه بار ناگهانی شود که منجر به تایم‌اوت و خطاهای 429 Too Many Requests می‌شود. Semaphore مانند یک فیوز عمل می‌کند و صرفاً تعداد مشخصی فراخوانی همزمان را بدون توجه به تعداد نخ‌های فعال عبور می‌دهد.

Semaphore در Kotlin برای Android

Android کلاس Semaphore را از بسته java.util.concurrent فراهم می‌کند. بیایید مثالی از محدود کردن درخواست‌های شبکه همزمان به دو نخ برای جلوگیری از اضافه بار سرور را بررسی کنیم.

kotlin
class ApiClient {
    private val throttle = Semaphore(2)

    suspend fun fetch(url: String): Result {
        throttle.acquire()
        return try {
            httpGet(url)
        } finally {
            throttle.release()
        }
    }
}

DispatchSemaphore در Swift برای iOS

در iOS، DispatchSemaphore از GCD همان وظیفه را حل می‌کند. توسعه‌دهندگان از آن برای همگام‌سازی دسترسی به منابع در کد ناهمزمان بدون مسدود کردن نخ اصلی استفاده می‌کنند.

swift
let semaphore = DispatchSemaphore(value: 3)

func processBatch(_ items: [UIImage]) {
    for img in items {
        semaphore.wait()
        DispatchQueue.global().async {
            applyFilter(to: img)
            semaphore.signal()
        }
    }
}

خطاهای معمول هنگام کار با semaphore‌ها

رایج‌ترین خطا — release فراموش‌شده در هنگام استثنا. اگر نخ قبل از فراخوانی release با خطا خاتمه یابد، semaphore برای همیشه برای نخ‌های دیگر مسدود می‌ماند. برای آزادسازی تضمینی از try/finally یا defer استفاده کنید. مشکل دوم deadlock هنگام گرفتن چندین semaphore به ترتیب متفاوت توسط نخ‌های مختلف است.

الگوهای استفاده از semaphore‌ها

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

چندین الگوی اثبات‌شده برای استفاده از semaphore‌ها در پروژه‌های واقعی وجود دارد. دانستن آنها به جلوگیری از خطاهای معمول و ساخت سیستم‌های چندنخی قابل اعتماد کمک می‌کند.

محدودکننده نرخ (Rate Limiter)

Semaphore با مقدار اولیه N و release دوره‌ای از طریق تایمر محدود کردن نرخ درخواست‌ها به API را پیاده‌سازی می‌کند. مثلاً سرویس اجازه 10 درخواست در ثانیه می‌دهد: semaphore با 10 شروع می‌شود، هر درخواست شمارنده را کاهش می‌دهد، و یک TimerTask جداگانه یک بار در ثانیه شمارنده را به مقدار اولیه برمی‌گرداند. این هم از برنامه و هم از سرور در برابر اضافه بار محافظت می‌کند.

تولیدکننده-مصرف‌کننده از طریق semaphore‌ها

در مسئله کلاسیک تولیدکننده-مصرف‌کننده، دو semaphore بافر را کنترل می‌کنند: empty (مجوز نوشتن) و full (مجوز خواندن). تولیدکننده acquire روی empty و release روی full را فراخوانی می‌کند، مصرف‌کننده — برعکس. این طرح تضمین می‌کند که مصرف‌کننده هرگز بافر خالی را نخواند و تولیدکننده آن را پر نکند.

همین طرح زیربنای بافر محدود در سیستم‌های عامل — بافر حلقوی با اندازه ثابت است. در برنامه‌های موبایل، این الگو برای پردازش صف‌های تصاویر، فایل‌های ویدیویی و رویدادهای تحلیلی استفاده می‌شود.

Throttling درخواست‌های شبکه

Semaphore‌ها با موفقیت برای Throttling فراخوانی‌های شبکه در سرویس‌های پس‌زمینه استفاده می‌شوند. مثلاً یک برنامه تحلیلی بسته‌های رویداد را به سرور ارسال می‌کند. بدون محدود کردن نخ‌های همزمان، در بارهای اوج (راه‌اندازی برنامه، همگام‌سازی پس از آفلاین) تعداد درخواست‌های همزمان ممکن است از محدودیت‌های سرور تجاوز کند. Semaphore با مقدار اولیه 3 ارسال روان را تضمین می‌کند و از قفل شدن سمت سرور جلوگیری می‌کند.

سوالات متداول

تفاوت بین Semaphore و شمارنده معمولی چیست؟

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

آیا semaphore می‌تواند باعث deadlock شود؟

بله، deadlock هنگام گرفتن چندین semaphore به ترتیب متفاوت توسط نخ‌های مختلف امکان‌پذیر است. مثلاً نخ A S1 و سپس S2 را می‌گیرد، در حالی که نخ B — S2 و سپس S1 را. ترتیب یکسانی برای گرفتن همه semaphore‌ها در پروژه تعیین کنید.

هنگام acquire با شمارنده صفر چه اتفاقی می‌افتد؟

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

Binary Semaphore اساساً چه تفاوتی با Mutex دارد؟

تفاوت اصلی — مالکیت. Mutex فقط توسط نخ مالک آزاد می‌شود. Binary Semaphore می‌تواند توسط هر نخی آزاد شود که برای سیگنال‌دهی بین نخ‌ها مناسب است اما برای محافظت از یکپارچگی داده‌ها کمتر امن است.

چه مقدار اولیه‌ای برای شمارنده انتخاب کنیم؟

مقدار اولیه به سناریو بستگی دارد. برای محافظت از یک منبع — 1. برای استخر N اتصال — N. برای سیگنال‌دهی بین نخ‌ها از 0 استفاده کنید تا نخ مصرف‌کننده منتظر سیگنال تولیدکننده بماند.

خلاصه

  • Semaphore — یک اولیه همگام‌سازی مبتنی بر شمارنده که توسط دیکسترا در سال 1965 پیشنهاد شد.
  • Semaphore دودویی مقادیر 0 و 1 را می‌گیرد، semaphore شمارشی — هر مقدار غیرمنفی.
  • عملیات acquire و release اتمی و نخ‌ایمن هستند.
  • برخلاف mutex، semaphore به نخ مالک وابسته نیست.
  • Semaphore‌های شمارشی برای مدیریت استخر منابع و محدود کردن موازی‌سازی استفاده می‌شوند.
  • Release فراموش‌شده — رایج‌ترین خطا که منجر به هنگ کردن نخ‌ها می‌شود.

ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد

IT Sectr از سال 2017 برنامه‌های iOS و Android را برای استارتاپ‌ها و کسب‌وکارها ایجاد می‌کند. ما به شما مشاوره می‌دهیم و بهترین راه‌حل را پیشنهاد خواهیم کرد.

بحث درباره پروژه

همچنین بخوانید