Preface
This article contains large scale extraction from APUE (3ed). I will not put this article into commercial use.
Why Threading
Simpler Programming Model
A synchronous programming model is much simpler than an asynchronous programming model.
Easier Access to Resources
Multiple processes have to use complex mechanisms provided by the OS to share memory and file descriptors. Threads, in contrast, automatically have access to the same memory address space and file descriptors.
Recall: Almost every multi-process programming involves
fork()
.fork()
duplicates memory space and file descriptors instead of sharing them.
Improved Information Throughput
Some problems can be partitioned so that overall program throughput can be improved. With multiple threads of control, the processing of independent tasks can be interleaved by assigning a separate thread per task.
Two tasks can be interleaved only if they don’t depend on the processing performed by each other.
Improved Response Time
Interactive programs can realize improved response time by using multiple threads to separate the portions of the programming that deal with user input and output from the other parts of the program.
NOTE: The benefits of a multithreaded programming model can be realized on a uniprocessor, or on multiprocessors/multicores.
1. Regardless of the number of processors, a program can be simplified.
2. As long as your program has to block when serializing tasks, you can still see improvements in response time and throughput when running on a uniprocessor, because some threads might be able to run while others are blocked.
Thread Information
What a thread consists
A thread consists of the information necessary to represent an execution context within a process:
1. thread ID
2. register values
3. stack
4. scheduling priority & policy
5. signal mask
6. errno
variable
7. thread-specific data (中: 线程私有数据)
Thread ID
The thread ID has significance only within the context of the process to which it belongs.
Comparing thread IDs:
#include <pthread.h>
int pthread_equal(pthread_t tid1, pthread_t tid2);
Obtaining self thread ID:
#include <pthread.h>
int pthread_self();
Thread Creation
Additional threads can be created by calling the pthread_create
function.
#include <pthread.h>
int pthread_create(pthread_t* tidp,
const pthread_attr_t* attr
void* (*start_rtn)(void*),
void* arg);
NOTE:
1. The newly created thread starts running at the address of the function start_rtn
.
2. If you need to pass more than one argument to start_rtn
, then you first need to encapsulate them into a struct
and pass its address;
3. There is no guarantee which will run first: the new one or the calling thread.
4. The newly created thread has access to the process address space, and inherits the calling thread’s floating-point environment and signal mask.
5. For the newly created thread, the set of pending signals is cleared.
What is a floating-point environment ?
浮点数环境
A floating-point environment is a set of floating-point states, which includes:
1. whether or not a thread can access the floating-point environment;
2. floating point exceptions(e.g. overflow, underflow, divide by zero, etc.)
3. rounding rules.
Thread Termination
Thread & exit
If any thread within a process calls exit
, _Exit
or _exit
, the entire process terminates.
A single thread can exit in three ways without terminating the entire process:
1. Return: The thread can simply return from the start routine, whose return value is the thread’s exit code.
2. Cancelation: The thread can be canceled by another thread in the same process.
3. API: The thread can call pthread_exit.
How to fetch the exit code of a thread
First we need to know the pthread_join
interface:
#include <pthread.h>
int pthread_join(pthread_t thread, void** rval_ptr);
The calling thread will block until the specified thread terminates in one of the three ways mentioned above.
Then we need to know how to set the exit code of a thread:
For example, by calling
pthread_exit((void*) 2);
you have the thread exit with exit code 2.
Finally we can study the following stereotype of fetching the exit code:
// This is the thread you are watching
pthread_t tid;
// This void* variable is used to store exit code
void* thread_return;
/*
* Some business logic
*/
// The exit code of tid is written to thread_return
pthread_join(tid, &thread_return);
NOTE: It is not recommended to return the address of local variable as exit code. The chances are, the stack of other threads may reuse the stack of the thread just terminated, thus causing unsafe memory access.
See Figure 11-4 of APUE, 3rd edition
How to cancel a thread
One thread can request that another thread in the same process be canceled by calling the pthread_cancel
function.
#include <pthread.h>
int pthread_cancel(pthread_t tid);
NOTE: pthread_cancel
doesn’t wait for the thread to terminate; it merely makes the request.
How to clean up a thread
A thread can arrange for functions to be called when it exits, similar to the way that the atexit
does for a process.
#include <pthread.h>
void pthread_cleanup_push(void(* rtn)(void*), void* arg);
void pthread_cleanup_pop(int execute);
The functions that passed to this function are known as thread cleanup handlers. More than one handler can be established for a thread. The handlers are recorded in a stack, which means that they are executed in the reverse order from that with which they were registered.
The cleanup handlers are called when the thread performs one of the following actions:
* Makes a call to pthread_exit
* Responds to a calcellation request
* Makes a call to pthread_cleanup_pop
with a nonzero execute
argument.