Search

Sponsored Links

Meta

Categories

Archives

Recent Posts

RSS Feeds

02
Nov

Multithreaded Programming using POSIX pthreads – Part 1

Related Blog Items


?

Introduction

Most of todays code is sequential/serialized, i.e. code is executed instruction by instruction, one after next. As humans tend to think sequentially, they put their thought process into programs, which essentially reflects their thought process. But the demand for multithreaded programming is increasing as to realize the advantage of Symmetric-Multiprocessing machines. You might have got a doubt, whether multithreaded program is useless on uniprocessing machines? The answer is even on uniprocessing machines they are helpful, say one thread blocked by a blocking call, other threads can use the CPU. However multiprocessing machine can yield full potential.
What is a Thread?
A thread is a sequence of instructions that can be executed in parallel with other threads. A thread is a light weight process, that means thread doesnt take as much space taken by a process, it light weight, of course its behavior is also little different from process. Before talking about threads in detail, lets talk about processes a bit.
A process is created by the operating system, and requires a fair amount of “overhead”. Processes contain information about program resources and program execution state, including:
Process ID, process group ID, user ID, and group ID
Environment
Working directory.
Program instructions
Registers
Stack
Heap
File descriptors
Signal actions
Shared libraries
Inter-process communication tools (such as message queues, pipes, semaphores, or shared memory).
Processes are scheduled by operating system. A process has these fundamental parts: code (”text”), data (VM), stack, file I/O, and signal tables. A typical process is shown below in fig1.

tfig1

So, we said processes have fair amount of overhead, so whats the overhead? Heavy-weight processes? have a significant amount of overhead when switching: all the tables have to be flushed from the processor for each task switch. Also, the only way to achieve shared information between processes is through pipes and “shared memory”. If a process spawns a child entire process space is duplicated. Cloning/Spawning processes take huge resources as processes are heavy weight.
So, now how threads can overcome this difficulty? Threads reduce overhead by sharing fundamental parts. By sharing these parts, switching happens much more frequently and efficiently.

?

Threads use and exist within the process resources, yet are able to be scheduled by the operating system and run as independent entities largely because they duplicate only the bare essential resources that enable them to exist as executable code.
This independent flow of control is accomplished because a thread maintains its own:

  • Stack pointer
  • Registers
  • Scheduling properties (such as policy or priority)
  • Set of pending and blocked signals
  • Thread specific data.

Threads in a process shown in fig2.
? tfig2

Types of Threads
There are two types of threads
Kernel-level threads

User-level threads
Kernel-Level Threads:
Kernel level threads consist of a set of registers, a stack, and a few corresponding kernel data structures. When kernel threads are used, the operating system will have a descriptor for each thread belonging to a process and it will schedule all the threads. Unlike processes, all threads within a process share the same address space. Similar to processes, when a kernel thread makes a blocking call, only that thread blocks.
The advantage of kernel threads over processes is faster creation and context switching compared with processes. For shared-memory multiprocessor architectures, the kernel is able to dispatch threads of one process on several processors, which leads to automatic load balancing within the nodes. For parallel programming, threads allow different parts of the parallel program to communicate by directly accessing each others’ memory, which allows very efficient, fine-grained communication.
Kernel threads share a single copy of the entire address space, including regions such as global data that may cause conflicts if used by multiple threads simultaneously. Threads can also cause unintentional data sharing, which leads to corruption and race conditions. To avoid this unintentional sharing, programs must often be modified to either lock or access separate copies of common data structures. Several very widely used language features are unsafe when used with threads, such as the use of global and static variables, or the idiom of returning a reference to a static buffer. Especially with large existing codebases with many global variables, this makes kernel threads very difficult to use because in most implementations of kernel threads, it is not possible to assign each thread a private set of global variables.
Kernel threads are considered “lightweight,” and one would expect the number of threads to only be limited by address space and processor time. Since every thread needs only a stack and a small data structure describing the thread, in principle this limit should not be a problem. But in practice, we found that many platforms impose hard limits on the maximum number of pthreads that can be created in a process.
In particular, operating system kernels tend to see kernel threads as a special kind of process rather than a unique entity. For example, in the Solaris kernel threads are called “light weight processes” (LWP’s). Linux actually creates kernel threads using a special variation of fork called “clone,” and until recently gave each thread a separate process ID. Because of this heritage, in practice kernel threads tend to be closer in memory and time cost to processes than user-level threads, although recent work has made some progress in closing the gap, including the Native POSIX Threading Library (NPTL) and Linux O(1) scheduler.


User-Level Threads:
Like a kernel thread, a user-level thread includes a set of registers and a stack, and shares the entire address space with the other threads in the enclosing process. Unlike a kernel thread, however, a user-level thread is handled entirely in user code, usually by a special library that provides at least start, swap and suspend calls. Because the OS is unaware of a user-level thread’s existence, a user-level thread can not separately receive signals or use operating system scheduling calls such as sleep(). Many implementations of user-level threads exist, including: GNU Portable Threads (Pth)? , FreeBSD’s userland threads, QuickThreads and those developed by us for the Charm++ system.
The primary advantages of user-level threads are efficiency and flexibility. Because the operating system is not involved, user-level threads can be made to use very little memory, and can be created and scheduled very quickly. User-level threads are also more flexible because the thread scheduler is in user code, which makes it much easier to schedule threads in an intelligent fashion — for example, the application’s priority structure can be directly used by the thread scheduler.
The primary disadvantage of user-level threads compared to kernel threads is the lack of operating system support. For example, when a user-level thread makes a blocking call, the kernel does not start running another user-level thread. Instead, the kernel suspends the entire calling kernel thread or process, even though another user-level thread might be ready to run. To avoid this blocking problem, some systems such as AIX and Solaris support “N:M” thread scheduling, which maps some number of application threads onto a (usually smaller) number of kernel entities. There are two parties, the kernel and the user parts of the thread system, involved in each thread operation for N:M threading, which is complex. The blocking problem can also be avoided by building a smarter runtime layer which intercepts blocking calls, replaces them with a non-blocking call, and starts another user-level thread while the call proceeds. Yet another approach is to provide support in the kernel to notify the user-level scheduler when a thread blocks, often called “scheduler activations”.
Since user-level threads are controlled in user code, there is virtually no limit on the maximum number of threads as long as the resource allows. In practice, one can create 50,000 user-level threads on a Linux box very easily.
Combination
Some implementations support both user- and kernel-level threads. This gives the advantages of each to the running task. However, since Linux’s kernel-level threads nearly perform as well as user-level, the only advantage of using user-threads would be the cooperative multitasking.

?

Popularity: 11%

You need to log on to convert this article into PDF


Related Blog Items

No Comments

No comments yet.

Leave a comment

*
To prove you're a person (not a spam script), type the security word shown in the picture.
Anti-spam image