Windows Thread Synchronization 13

 

 

 

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:

 

  1. The WaitForSingleObject()
  2. The WaitForMultipleObjects()
  3. Again, waiting for Multiple Objects
  4. Using Named Objects
  5. Using Event Objects
  6. Using Mutex Objects
  7. Another Mutex Example
  8. Using Semaphore Objects
  9. Another Semaphore Example
  10. Six people with six chopsticks
  11. Using Waitable Timer Objects
  12. Using Waitable Timers with an Asynchronous Procedure Call
  13. Using Critical Section Objects
  14. Another Critical Section Program Example
  15. More Critical Section Example
  16. Using Condition Variables
  17. Using One-Time Initialization
  18. Synchronous Example
  19. Asynchronous Example
  20. Using Singly Linked Lists
  21. Using Timer Queues
  22. The Interlocked Functions Example 1
  23. The Interlocked Functions Example 2
  24. The Interlocked Functions Example 3

 

When you try the following program examples, observing the outputs, consider the following information.

  1. 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).

  2. The traditional program is one thread per process where the main thread starts with main() process.

  3. Keep in mind that only one thread (or PC) is allowed to execute the code segment of the program at any time.

  4. A thread is an execution path in the code segment while OS provides an individual PC for each execution path.

  5. Thread is a kernel object which can be owned by several owners.

  6. Each thread when created is owned by two owners, the creator and the thread itself. So, the reference count is two.

  7. Context switching may occurs in the middle of printf()/wprintf() command which may result a race condition. Context switching may occur in any time.

  8. In multithreading, typically we do not have the ability to control the output sequences or scheduling.

  9. The multithreading program becomes concurrent and nondeterministic. Same input to a concurrent program may generate different outputs.

  10. 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.

 

The WaitForSingleObject() Example: Creating new Win32 empty console application project

 

Then, add the source file and give it a suitable name.

 

The WaitForSingleObject() Example: Adding new C++ source file

 

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.

 

The WaitForSingleObject() Example: Sample console program output

 

 

 

< Thread Synchronization 12 | Thread Synchronization Programming | Win32 Programming | Thread Synchronization 14 >