Starvation
Starvation describes a situation where a thread is unable to gain regular access to shared resources and is unable to make progress. This happens when shared resources are made unavailable for long periods by greedy threads. For example, suppose an object provides a synchronized method that often takes a long time to return. If one thread invokes this method frequently, other threads that also need frequent synchronized access to the same object will often be blocked.
Livelock
A thread often acts in response to the action of another thread. If the other thread's action is also a response to the action of another thread, then livelock may result. As with deadlock, livelocked threads are unable to make further progress. However, the threads are not blocked; they are simply too busy responding to each other to resume work. A livelock happens when a request for an exclusive lock to use the shared resource is repeatedly denied because a series of overlapping shared locks keeps interfering and at the end two or more threads continue to execute, but make no progress in completing their tasks. Synchronization objects can be used to solve the race condition and deadlock. The solutions always refer to the code that using safe thread. These issues should be more obvious in the multithreaded programming.
Program Examples on Using Synchronization Objects
The following examples demonstrate how to use the synchronization objects:
When you try the following program examples, observing the outputs, consider the following information.
Multithread means the ability of an OS to support multiple threads of execution within a single process (in this case, there should be many Program Counters (PC) in the code segment).
The traditional program is one thread per process where the main thread starts with main() process.
Keep in mind that only one thread (or PC) is allowed to execute the code segment of the program at any time.
A thread is an execution path in the code segment while OS provides an individual PC for each execution path.
Thread is a kernel object which can be owned by several owners.
Each thread when created is owned by two owners, the creator and the thread itself. So, the reference count is two.
Context switching may occurs in the middle of printf()/wprintf() command which may result a race condition. Context switching may occur in any time.
In multithreading, typically we do not have the ability to control the output sequences or scheduling.
The multithreading program becomes concurrent and nondeterministic. Same input to a concurrent program may generate different outputs.
Using many global variables and static variables may result those threads to race each other hence better to avoid using global variables among threads.
The wait functions are used to block a thread from executing till a kernel object reaches a certain state. The wait function blocks when the object is in an unsignaled state and resumes when the object reaches a signaled state. The signal and unsignaled states are dependent on the object. For example, the unsignaled state of a thread is when the thread has not yet terminated and in signaled state when it terminates. The WaitForSingleObject() and WaitForMultipleObjects() are both wait functions. The WaitForSingleObject() function is passed a handle to single kernel object. The function blocks till the single kernel object is signaled. The WaitForMultipleObjects() is used to block on a group of kernel objects. This function can block till either one of the group of kernel objects is signaled or when all the kernel objects are signaled. Both functions can accept a time out value. If an object does not reach a signaled state within a certain amount of time (time out value) both functions return and execution resumes. The following program demonstrates a primary thread blocking (waiting) till a child thread has finished execution.
The WaitForSingleObject() Example
Create a new empty Win32 console application project. Give a suitable project name and change the project location if needed.
Then, add the source file and give it a suitable name.
Next, add the following source code.
// Primary thread waits till child thread terminates
#include <windows.h>
#include <stdio.h>
// Thread counts till PassVal
void ThreadMain(LONG PassVal)
{
LONG i;
for(i=0;i<PassVal;i++)
{
wprintf(LThreadMain() - count #%d\n, i);
}
}
// Main process & thread
int wmain(void)
{
DWORD ThreadID, dwRet;
HANDLE hTh;
// Creates a thread to execute within the virtual address space of the calling process
hTh=CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)ThreadMain,(LPVOID)10,0,&ThreadID);
if(hTh==NULL)
{
wprintf(LCreateThread() failed, error %u\n, GetLastError());
return 1;
}
else
wprintf(LCreateThread() is OK, thread ID %u\n, ThreadID);
// Blocks/wait till thread terminates
// Waits until the specified object is in the signaled state or
// the time-out interval elapses.
// The INFINITE parameter make the function return only when the object is signaled
wprintf(LWaiting the child thread terminates/signaled...\n);
dwRet = WaitForSingleObject(hTh,INFINITE);
wprintf(LWaitForSingleObject() returns value is 0X%.8X\n, dwRet);
switch(dwRet)
{
case WAIT_ABANDONED:
wprintf(LMutex object was not released by the thread that\n
Lowned the mutex object before the owning thread terminates...\n);
break;
case WAIT_OBJECT_0:
wprintf(LThe child thread state was signaled!\n);
break;
case WAIT_TIMEOUT:
wprintf(LTime-out interval elapsed, and the child thread's state is nonsignaled.\n);
break;
case WAIT_FAILED:
wprintf(LWaitForSingleObject() failed, error %u\n, GetLastError());
ExitProcess(0);
}
if(CloseHandle(hTh) != 0)
wprintf(LhTh's child thread handle was closed successfully!\n);
else
wprintf(LFailed to close hTh child thread handle, error %u\n, GetLastError());
wprintf(LMain process & thread ready to exit...\n);
return 0;
}
Build and run the project. The following screenshot is a sample output.