Thread synchronization

Thread synchronization

As we know, to run threads, we need to schedule them. In order to run them effectively, they need to be synchronized. Suppose one thread creates a brush and then creates several threads that share the brush and draw with it. The first thread must not destroy the brush until the other threads finish drawing. This requires a means of coordinating the sequence of actions in several threads.

 

One way is to create a global Boolean variable that one thread uses to signal another. The writing thread will set this parameter to TRUE and the reading thread might loop until it sees the flag change. This will definitely work, but the looping thread wastes a lot of processor time.

 

Instead, Win32 supports a set of synchronization objects such as mutexes, semaphores, events and critical sections. These are the system objects created by the object manager. All of them will work in a similar way. A thread that wants to perform some coordinated action waits for a response from one of these objects and proceeds only after receiving it. The scheduler removes the waiting objects from the dispatch queue so that they will not consume processor time

 

It is important to keep in mind that:

• A mutex object works like a narrow gate for one thread to pass at a time.

• A semaphore object work like a multi-lane gate that a limited number of threads can pass through together.

• An event object broadcasts a public signal for any listening thread to hear.

• A critical section object works like a mutex but only within a single process.

 

Mutexes, semaphores and events can coordinate threads in different processes, but critical sections are only visible to threads in a single process.

 

Mutexex: These are very much like critical sections except that they can be used to synchronize data across multiple processes. To do this, a thread in each process must have its own process-relative handle to a single mutex object.

 

Semaphores: These objects are used for resource counting. They offer a thread the ability to query the number of resources available; if one or more resources are available, the count of available resources is decremented. Thus semaphores perform the test and set operations automatically, that is, when you request a resource from a semaphore, the operating system checks whether the resource is available and decrements the count of the available resources without letting another thread interfere. Only after the resource count has been decremented does the system allow another thread to request a resource.

 

For example, let us say that a computer has three serial ports. No more than three threads can use the serial ports at any given time; each port can be assigned to one thread. This situation provides a perfect opportunity to use a semaphore. To monitor serial port usage, you can create a semaphore with a count of three – one for each port. A semaphore is signaled when its resource count is greater than zero and is non-signaled when the count is zero.

Because several threads can affect a semaphore’s resource count, a semaphore, unlike a critical section or mutex, is not considered to be owned by a thread. This means that it is possible to have one thread wait for the semaphore object and another thread release the object.

 

Events: Even objects are the most primitive form of synchronization objects and they are quite different from mutexes and semaphores. Mutexes and semaphores are usually used to control access to data, but events are used to signal that some operation has been completed.

 

There are two different types of event objects – manual reset events and auto reset events. A manual reset event is used to signal several threads simultaneously to say that an operation has finished, and an auto reset event is used to signal a single thread to say that an operation has been completed.

 

Events are most commonly used when one thread performs initialization work and, when it finishes, signals another thread to perform the remaining work. The initialization thread sets the event to the non-signaled state and begins to perform the initialization. Then, after the initialization has been completed, the thread sets the event to the signaled state. Waiting for the event, the worker thread wakes up and performs the rest of the work.

 

For example, a process might be running two threads. The first thread reads data from a file into a memory buffer. After the data has been read, the first thread signals the second thread that it can process the data. When the second thread finishes processing the data, it might need to signal the first thread again, so that the first thread can read the next block of data from the file.

 

Critical sections: A critical section is a small section of the code that required exclusive access to some shared data before the code can execute. Of all synchronization objects, critical sections are the simplest to use, but they can be used to synchronize threads only within a single process. Critical sections allow only one thread at a time to gain access to a region of data.

 

Mutex Vs semaphore

A semaphore can be a Mutex but a Mutex can never be semaphore. This simply means that a binary semaphore can be used as Mutex, but a Mutex can never exhibit the functionality of semaphore.Both semaphores and Mutex (at least the on latest kernel) are non-recursive in nature.No one owns semaphores, whereas Mutex are owned and the owner is held responsible for them. This is an important distinction from a debugging perspective.

 

In case the of Mutex, the thread that owns the Mutex is responsible for freeing it. However, in the case of semaphores, this condition is not required. Any other thread can signal to free the semaphore by using the sem_post() function.

 

A Mutex, by definition, is used to serialize access to a section of re-entrant code that cannot be executed concurrently by more than one thread. A semaphore, by definition, restricts the number of simultaneous users of a shared resource up to a maximum number

 

Another difference that would matter to developers is that semaphores are system-wide and remain in the form of files on the filesystem, unless otherwise cleaned up. Mutex are process-wide and get cleaned up automatically when a process exits.

 

The nature of semaphores makes it possible to use them in synchronizing related and unrelated process, as well as between threads. Mutex can be used only in synchronizing between threads and at most between related processes (the pthread implementation of the latest kernel comes with a feature that allows Mutex to be used between related process).

 

According to the kernel documentation, Mutex are lighter when compared to semaphores. What this means is that a program with semaphore usage has a higher memory footprint when compared to a program having Mutex.

From a usage perspective, Mutex has simpler semantics when compared to semaphores.

 

A mutex is like a lock. A thread can lock it, and then any subsequent attempt to lock it, by the same thread or any other, will cause the attempting thread to block until the mutex is unlocked. These are very handy for keeping data structures correct from all the threads’ points of view. For example, imagine a very large linked list. If one thread deletes a node at the same time that another thread is trying to walk the list, it is possible for the walking thread to fall off the list, so to speak, if the node is deleted or changed. Using a mutex to “lock” the list keeps this from happening.

Computer Scientist people will tell you that Mutex stands for Mutual Exclusion.

In Java, Mutex-like behaviour is accomplished using the synchronized keyword.

Technically speaking, only the thread that locks a mutex can unlock it, but sometimes operating systems will allow any thread to unlock it. Doing this is, of course, a Bad Idea. If you need this kind of functionality, read on about the semaphore in the next paragraph.

Similar to the mutex is the semaphore. A semaphore is like a mutex that counts instead of locks. If it reaches zero, the next attempt to access the semaphore will block until someone else increases it. This is useful for resource management when there is more than one resource, or if two separate threads are using the same resource in coordination. Common terminology for using semaphores is “uping” and “downing”, where upping increases the count and downing decreases and blocks on zero.

Java provides a Class called Semaphore which does the same thing, but uses acquire() and release() methods instead of uping and downing.

With a name as cool-sounding as semaphore, even Computer Scientists couldn’t think up what this is short for.

Unlike mutexes, semaphores are designed to allow multiple threads to up and down them all at once. If you create a semaphore with a count of 1, it will act just like a mutex, with the ability to allow other threads to unlock it.

Computer Scientists like to refer to the pieces of code protected by mutexes and semaphores as Critical Sections. In general, it’s a good idea to keep Critical Sections as short as possible to allow the application to be as paralle as possible.

Comments