Skip to content

Latest commit

 

History

History

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 

README.md

C++ Counting Semaphore Example

The counting semaphore is a fundamental synchronization primitive in computer science, first introduced by Dutch computer scientist Edsger W. Dijkstra in 1965. Unlike a mutex (binary semaphore) which allows only one thread to access a resource at a time, a counting semaphore allows up to a specified number of threads to access shared resources concurrently. It maintains an internal counter that gets decremented when a thread acquires the semaphore and incremented when a thread releases it. If the counter would become negative upon acquisition, the requesting thread is blocked until another thread releases the semaphore, incrementing the counter back to a non-negative value.

Use Cases and Problem Solving

Counting semaphores are versatile synchronization tools that solve several critical concurrency challenges. They excel at managing access to a pool of identical resources, such as connection pools, thread pools, or buffer pools. For example, a database connection pool might use a counting semaphore to limit the maximum number of simultaneous connections. They're also essential in producer-consumer scenarios where you need to limit the number of items in a queue or buffer. Counting semaphores can implement throttling mechanisms to limit the rate of operations, particularly useful in networking applications or I/O operations where system resources are finite. They also provide an elegant solution for implementing barriers and rendezvous points in parallel programming, where a specific number of threads must reach a certain point before any can proceed.

Implementation in Modern C++

In C++20, std::counting_semaphore was introduced into the standard library, providing a portable and efficient implementation of this pattern. This implementation includes both non-blocking (try_acquire) and blocking (acquire) methods, making it versatile for different concurrency needs.

Basic usage pattern:

// Create a semaphore allowing up to 5 concurrent accesses
std::counting_semaphore<5> sem(5);

// Acquire the semaphore (blocks if count would go below 0)
sem.acquire();

// Try to acquire without blocking (returns false if would block)
if (sem.try_acquire()) {
    // Resource acquired
} else {
    // Could not acquire resource
}

// Release the semaphore
sem.release();

Advantages

  • Allows precise control over concurrent access to resources
  • More flexible than mutexes for managing pools of resources
  • Provides both blocking and non-blocking acquisition options
  • Thread-safe and efficient implementation in the standard library
  • Can be used to implement various concurrent design patterns

Limitations

  • Misuse can lead to deadlocks if resources are not properly released
  • Incorrect initial count can lead to unexpected behavior
  • Does not inherently provide recursive acquisition like some mutex implementations
  • The template parameter specifies the maximum value, which must be known at compile time

Best Practices

  • Always match acquire calls with release calls
  • Consider using RAII wrappers to ensure resources are released
  • Be careful with exception safety to prevent resource leaks
  • Use non-blocking acquisition when appropriate to prevent deadlocks
  • Document the purpose and ownership rules of your semaphores

Further Reading

  • "The Little Book of Semaphores" by Allen B. Downey
  • "C++ Concurrency in Action" by Anthony Williams
  • "Operating Systems: Three Easy Pieces" by Remzi H. Arpaci-Dusseau and Andrea C. Arpaci-Dusseau
  • "Concurrency: The Works of Leslie Lamport"
  • "Modern C++ Programming with Test-Driven Development" by Jeff Langr

License

This code is provided under the MIT License. Feel free to use, modify, and distribute as needed.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.