Семафоры

Классический механизм синхронизации в многопоточном программировании, который позволяет ограничивать доступ к общим ресурсам определенным количеством потоков. Семафоры поддерживают две основные операции: захват (acquire) и освобождение (release).

  • Операция захвата (acquire) уменьшает счетчик семафора на 1. Если счетчик семафора становится отрицательным, поток, который выполняет захват, блокируется до тех пор, пока счетчик не станет положительным.
  • Операция освобождения (release) увеличивает счетчик семафора на 1. Если есть заблокированные потоки, один из них будет разблокирован.

Семафоры могут использоваться для координации доступа к общим ресурсам, ограничения количества потоков, имеющих доступ к критическим секциям, и для решения проблемы "производительность-честность".

Примеры использования семафоров:

  1. Ограничение доступа к ресурсам
import java.util.concurrent.Semaphore

val semaphore = Semaphore(3) // Разрешаем одновременно не более 3 потоков

fun main() {
    val threads = List(10) {
        Thread {
            semaphore.acquire()
            println("Thread ${Thread.currentThread().name} acquired semaphore")
            Thread.sleep(1000)
            semaphore.release()
            println("Thread ${Thread.currentThread().name} released semaphore")
        }
    }

    threads.forEach { it.start() }
    threads.forEach { it.join() }
}
  1. Использование семафора для синхронизации потоков:
import java.util.concurrent.Semaphore

val semaphore = Semaphore(0) // Изначально семафор заблокирован

fun main() {
    val threads = List(3) {
        Thread {
            println("Thread ${Thread.currentThread().name} is waiting")
            semaphore.acquire()
            println("Thread ${Thread.currentThread().name} is released")
        }
    }

    threads.forEach { it.start() }
    Thread.sleep(3000) // Подождем немного
    semaphore.release(3) // Разблокируем 3 потока
}

Когда использовать семафоры:

  1. Когда нужно ограничить доступ к общим ресурсам: Например, когда есть ограниченное количество подключений к базе данных или потоков, которые могут обращаться к файловой системе.
  2. Когда нужно реализовать ограничение на количество потоков, выполняющих определенную операцию: Например, ограничение на количество потоков, выполняющих сетевые запросы к внешнему сервису.
  3. Когда требуется синхронизация потоков в определенном порядке: Например, когда необходимо дождаться выполнения нескольких операций в определенном порядке, прежде чем продолжить выполнение.

Когда не использовать семафоры:

  1. Для простых операций блокировки и разблокировки: Если нужно просто заблокировать выполнение потока до завершения какой-то операции, лучше использовать другие механизмы, такие как мониторы или блокировки.
  2. Когда есть риск гонок данных или взаимоблокировки: Неправильное использование семафоров может привести к гонкам данных или взаимоблокировке, поэтому нужно быть осторожным и тщательно проектировать сценарии использования.