Thread concurrency mutex semaphore
Threads are lightweight processes that share the same address space(of a parent process), therefore can context-switch really fast. Process is an instance of a proram that is being executed. Processes are generally made up of threads. There are kernel and user threads in unix that are called kthread and pthread.
Mutex, only the thread that locked or acquired the mutex can unlock it. In the case of a semaphore, a thread waiting on a semaphore can be signaled by a different thread. Some operating system supports using mutex & semaphores between process.
Threads, also known as light weight processes are the basic unit of CPU initialization. So, why do we call them as light weight processes? One of the reason is that the context switch between the threads takes much lesser time as compared to processes, which results from the fact that all the threads within the process share the same address space, so you don?t need to switch the address space.
In the user space, threads are created using the POSIX APIs and are known as pthreads. Some of the advantages of the thread, is that since all the threads within the processes share the same address space, the communication between the threads is far easier and less time consuming as compared to processes. And usually is done through the global variables. This approach has one disadvantage though. It leads to several concurrency issues and require the synchronization mechanisms to handle the same.
Having said that, why do we require threads? Need for the multiple threads arises, when we need to achieve the parallelism within the process. To give you the simple example, while working on the word processor, if we enable the spell and grammar check, as we key in the words, you will see red/green lines appear, if we type something syntactically/grammatically incorrect. This can most probably be implemented as threads.
The above material comes from here: sysplay.in
Just as a process is identified through a process ID, a thread is identified by a thread ID. Suppose there is a case where a link list contains data for different threads. Every node in the list contains a thread ID and the corresponding data. Now whenever a thread tries to fetch its data from linked list, it first gets its own ID by calling ?pthread_self()? and then it calls the ?pthread_equal()? on every node to see if the node contains data for it or not.
- A process ID is unique across the system where as a thread ID is unique only in context of a single process.
- A process ID is an integer value but the thread ID is not necessarily an integer value. It could well be a structure
- A process ID can be printed very easily while a thread ID is not easy to print.
POSIX User Space Threads Example
$ ./threads Thread created successfully First thread processing Thread created successfully Second thread processing
Tips: pthread_join(thread_id) for main to wait for the worker thread to finish.
Pthread Sample 2
Kernel threads are same as user space threads in many aspects, but one of the biggest difference is that they exist in the kernel space and execute in a privileged mode and have full access to the kernel data structures. These are basically used to implement background tasks inside the kernel. The task can be handling of asynchronous events or waiting for an event to occur. Device drivers utilize the services of kernel threads to handle such tasks. For example, the ksoftirqd/0 thread is used to implement the Soft IRQs in kernel. The khubdkernel thread monitors the usb hubs and helps in configuring usb devices during hot-plugging.
APIs for creating the Kernel thread
Below is the API for creating the thread:
- function: The function that the thread has to execute
- data: The data? to be passed to the function
- name: The name by which the process will be recognized in the kernel
- Retuns: Pointer to a structure of type task_struct
Below is an example code which creates a kernel thread:
In the above code, thread is created in the init_thread(). Created thread executes the function thread_fn(). Compile the above code and insert the module with insmod. Below is the output, you get: Thread Created successfully Thats all we get as an output. Now, you might be wondering why is thread_fn() not executing? The reason for this is, when we create the thread with kthread_create(), it creates the thread in sleep state and thus nothing is executed. So, how do we wake up the thread. We have a API wake_up_process() for this. Below is modified code which uses this API.
As you might notice, wake_up_process() takes pointer to task_struct as an argument, which in turn is returned from kthread_create(). Below is the output:
Thread Created successfully Thread Running Thread Running ...
As seen, running a thread is a two step process ? First create a thread and wake it up using wake_up_process(). However, kernel provides an API, which performs both these steps in one go as shown below:
function ? The function that the thread has to execute data ? The ?data? to be passed to the function name ? The name by which the process will be recognized in the kernel Returns: Pointer to a structure of type task_struct So, just replace the kthread_create() and wake_up_process() calls in above code with kthread_run and you will notice that thread starts running immediately.
“mutex” is the actual low-level synchronizing primitive. You can take a mutex and then release it, and only one thread can take it at any single time (hence it is a synchronizing primitive). A recursive mutex is one which can be taken by the same thread multiple times, and then it needs to be released as many times by the same thread before others can take it. A “lock” here is just a C++ wrapper class that takes a mutex in its constructor and releases it at the destructor. It is useful for establishing synchronizing for C++ scopes. A condition variable is a more advanced / high-level form of synchronizing primitive which combines a lock with a “signaling” mechanism. It is used when threads need to wait for a resource to become available. A thread can “wait” on a CV and then the resource producer can “signal” the variable, in which case the threads who wait for the CV get notified and can continue execution. A mutex is combined with CV to avoid the race condition where a thread starts to wait on a CV at the same time another thread wants to signal it; then it is not controllable whether the signal is delivered or gets lost. By- Antti Huima https://stackoverflow.com/questions/1055398/differences-between-conditional-variables-mutexes-and-locks
Read more at:
A Mutex is a lock that we set before using a shared resource and release after using it. When the lock is set, no other thread can access the locked region of code. So we see that even if thread 2 is scheduled while thread 1 was not done accessing the shared resource and the code is locked by thread 1 using mutexes then thread 2 cannot even access that region of code. So this ensures a synchronized access of shared resources in the code.
Internally mutex works as follows:
- Suppose one thread has locked a region of code using mutex and is executing that piece of code.
- Now if scheduler decides to do a context switch, then all the other threads which are ready to execute the same region are unblocked.
- Only one of all the threads would make it to the execution but if this thread tries to execute the same region of code that is already locked then it will again go to sleep.
- Context switch will take place again and again but no thread would be able to execute the locked region of code until the mutex lock over it is released.
- Mutex lock will only be released by the thread who locked it.
- So this ensures that once a thread has locked a piece of code then no other thread can execute the same region until it is unlocked by the thread who locked it.
- Hence, this system ensures synchronization among the threads while working on shared resources.
A mutex is initialized and then a lock is achieved by calling the following two functions:
Mutex Thread Synchronization Example
In the code above :
- A mutex is initialized in the beginning of the main function.
- The same mutex is locked in the ?doSomeThing()? function while using the shared resource ?counter?
- At the end of the function ?doSomeThing()? the same mutex is unlocked.
- At the end of the main function when both the threads are done, the mutex is destroyed.
- Now if we look at the output, we find :
$ ./threads Job 1 started Job 1 finished Job 2 started Job 2 finished
So we see that this time the start and finish logs of both the jobs were present. So thread synchronization took place by the use of Mutex.