Programming With POSIX Threads

Chapter One

  • Asynchronous: two operation proceed independently of each other.
  • thread: just a more way to make application asynchronous
  • concurency does not imply that the operations proceed simultaneously
  • concurrency allows application to take adcantage of asynchronous capabilities and “do work” while independent operations are proceeding.
  • 并发的实质是一个物理CPU(也可以多个物理CPU) 在若干道程序之间多路复用,并发性是对有限物理资源强制行使多用户共享以提高效率。
  • 并行性指两个或两个以上事件或活动在同一时刻发生。在多道程序环境下,并行性使多个程序同一时刻可在不同CPU上同时执行。
  • Reentrant code should avoid relying on static data and, ideally, should avoid reliance on any form of synchronization between threads.
  • A system’s scheduling facility may allow each thread to run until it voluntarily yields the processor to another thread ("run until block").
  • It may provide time-slicing, where each thread is forced to periodically yield so that other threads may run `(“round-robin”)
  • A thread may have o processor status and coprocessor control registers.
  • A thread does not include most of the rest of the state associated with a process;
  • threads do not have their own file descriptors or address space.

gcc thread.c -o thread -lpthread

  • For very simple applications ,an event-based implementation may be simpler than the multiprocess or multithread variations

  • Some advantages of the multithreaded programming model follow:

    1. Exploitation of program parallelism on multiprocessor hardware. Parallelism is the only benefit that requires special hardware. The others can help most programs without specialhardware.
    2. More efficient exploitation of a program’s natural concurrency, by allowing the programto perform computations while waiting for slow I/O operations to complete.
    3. A modular programming model that clearly expresses relationships between independent “events” within the program. These advantages are detailed in the following sections.
  • It is easy to lose performance by using too much synchronization;

  • The performance suffers when the multithreaded implementation adds threadsynchronization and scheduling overhead to the work you are to accomplish.
  • Your most powerful and portable thread debugging tool is your mind

Chapter Two

1
2
3
4
5
6
7
8
9
10
11
pthread_t thread;
int pthread_equal (pthread_t t1, pthread_t t2);
//compare two thread identifiers
int pthread_create (pthread_t *thread, const pthread_attr_t *attr, void *(*start)(void *),void *arg);
pthread_t pthread_self (void);
//get its own identifier using the pthread\_self function
//returns a nonzero value if refer to the same thread, 0 if not the same
int sched_yield (void);
int pthread_exit (void *value_ptr);
int pthread_detach (pthread_t thread);
int pthread_join (pthread_t thread, void **value_ptr);
  • The initial thread is special because the process terminates without allowing other threads to complete
  • Detaching a thread tells the system that you no longer need that thread, and allows the system to reclaim the resources allocated to the thread.
  • Returning from main will cause the process to terminate, along with all threads.
  • code the main thread to terminate by calling pthread_exit, which would allow the process to continue until all threads have terminated.

    The most important thing to remember about thread creation is that there is no synchronization between the creating thread’s return from pthread_create and the scheduling of the new thread. That is, the thread may start before the creating thread returns. The thread may even run to completion and terminate before pthread_create returns

  • pthread_join is a convenience, not a rule.

    it is often at least as simple to create the thread detached and devise your own customized return mechanism as it is to use pthread_join. For example, broadcast a condition variable when done.

Chapter Three

  • Critical sections “serial regions” : areas of code that affect a shared state.
  • The most common and general way to synchronize between threads is to ensure that all memory accesses to the same (or related) data are “mutually exclusive.”

  • Creating and destroying a mutex

    1
    2
    3
    pthread_mutex_t mutex = PTHREAD_MUNEX_INITIALIZER;
    int pthread_mutex_init (pthread_mitex_t *mutex, pthread_mutexattr_t *attr);
    int pthread_mutex_destroy (pthread_mutex_t *mutex);
  • never make a copy of a mutex, the result of using a copied mutex is undefined.

  • It is a good idea to associate a mutex clearly with the data it protects, if possible, by keeping the definition of the mutex and data together
  • it is safe (and a good idea) to unlock and destroy the mutex before freeing the storage that the mutex occupies.
  • the mutex dynamically initialized by calling pthread_mutex_init should destroyed by calling pthread_mutex_destroy. but a mutex that was statically initialized using the PTHREAD_MUTEX_INITIALIZER macro not, wihch will be destroyed automatically.
1
2
3
int pthread_mutex_lock (pthread_mutex_t *mutex);
int pthread_mutex_trylock (pthread_mutex_t *mutex);
int pthread_mutex_unlock (pthread_mutex_t *mutex);

pthread_mutex_trylock function will return an error status (EBUSY) instead of blocking if the mutex is already locked.

  • A condition variable wait always returns with the mutex locked
  • The mutex must always be locked when you wait on a condition variable

Creating and destroying a condition variable

1
2
3
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int pthread_cond_init (pthread_cond_t *cond, pthread_condattr_t *condattr);
int pthread_cond_destroy (pthread_cond_t *cond);

  1. cond_wait 等待信号量,若未收到信号量,解锁mutex,并挂起到相应信号量等待队列原子操作(cond\_wait 不返回处于等待信号量状态,不占用CPU)
  2. 收到信号量,尝试上锁mutex,若锁被占用则等待,直到获得锁,若上锁成功,进入临界区使用条件变量
  3. 由外部unlock完成解锁

Waiting on a condition variable

1
2
int pthread_cond_wait (pthread_cond_t *cond, pthread_mutex_t *mutex);
int pthread_cond_timedwait (pthread_cond_t *cond, pthread_mutex_t *mutex, struct timespec *expiration);

  • Each condition variable must be associated with a specific mutex, and with a predicate condition.
  • That is, each condition variable must be associated, at any given time, with only one mutex–but a mutex may have any number of condition variables associated with it.

Waking condition variable waiters

1
2
int pthread_cond_signal (pthread_cond_t *cond);
int pthread_cond_broadcast (pthread_cond_t *cond);

Chapter five

  • once initialize
    1
    2
    pthread_once_t once_control = PTHREAD_ONCE_INIT;
    int pthread_once (pthread_once_t *once_control, void(*init_routine) (void));
  • First, you declare a control variable of type pthread_once_t. The control variable must be statically initialized using the PTHREAD_ONCE_INIT macro,
  • You must also create a function containing the code to perform all initialization that is to be associated with the control variable.
  • Now, at any time, a thread may call pthread_once, specifying a pointer to the control variable and a pointer to the associated initialization function.
  • only the first call pthread_once will successfully initialized
  • Mutex attributes
    1
    2
    3
    4
    5
    6
    7
    pthread_mutexattr_t attr;
    int pthread_mutexattr_init { pthread_mutexattr_t *attr);
    int pthread_mutexattr_destroy (pthread_mutexattr_t *attr);
    \#ifdef _POSIX_THREAD PROCESS SHARED
    int pthread_mutexattr_getpshared (pthread_mutexattr_t  *attr, int *pshared);
    int pthread_mutexattr_setpshared (pthread_mutexattr_t *attr, int pshared);
    endif
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <pthread.h>
#include "errors.h"
pthread_mutex_t mutex;
int main (int argc, char *argv[])
{
pthread_mutexattr_t mutex_attr;
int status;
status = pthread_mutexattr_init (& mutex\_attr);
if (status != 0)
err_abort (status, "Create attr");
#ifdef _POSIX_THREAD_PROCESS_SHARED
status = pthread_mutexattr_setpshared (
& mutex\_attr, PTHREAD_PROCESS_PRIVATE);
if (status != 0)err_abort (status, "Set pshared");
#endif
status = pthread_mutex_init (&mutex, &mutex_attr);
if (status != 0)
err_abort (status, "Init mutex");
return 0;
}
  • Condition variable attributes

    1
    2
    3
    4
    5
    6
    7
    pthread_condattr_t attr;
    int pthread_condattr_init (pthread_condattr_t *attr);
    int pthread_condattr_destroy (pthread_condattr_t *attr);
    #ifdef _POSIX_THREAD_PROCESS_SHARED
    int pthread_condattr_getpshared(pthread_condattr_t *attr, int *pshared);
    int pthread_condattr_setpshared (pthread_condattr_t *attr, int pshared);
    #endif
  • Thread attributes

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    pthread_attr_t attr;
    int pthread_attr_init (pthread_attr_t *attr);
    int pthread_attr_destroy (pthread_attr_t *attr);
    int pthread_attr_getdetachstate (pthread_attr_t *attr, int *detachstate);
    int pthread_attr_setdetachstate (pthread_attr_t *attr, int detachstate);
    #ifdef _POSIX_THREAD_ATTR_STACKSISE
    int pthread_attr_getstacksize (pthread_attr_t *attr, size_t *stacksize);
    int pthreae_attr_setstacksize (pthread_attr_t *attr, size_t stacksize);
    #endif
    #ifdef _POSIX_THREAD_ATTR_STACKADDR
    int pthread_attr_getstarkaddr (pthread_attr_t *attr, void *stackaddr);
    int pthread_attr_set-tackaddr (pthread_attr_t *attr, void **stackaddr);
    #endif
  • Cancellation

    1
    2
    3
    4
    5
    6
    int pthread_cancel (pthread_t thread);
    int pthread_setcancelstate (int state, int * oldstate);
    int pthread_setcanceltype (int type, int *oldstate);
    void pthread_testcancel (void);
    void pthread_cleanup_push (void (*routine)(void *), void *arg);
    void pthread_cleanup_pop (int execute);
  • Creating thread-specific data

    1
    2
    3
    pthread_key_t key;
    int pthread_key_create (pthread_key_t *key, void (*destructor)(void *));
    int pthread_key_delete (pthread_key_t key);
  • you should never delete a thread-specific data key until you are sure that no existing threads have a value for that key,
  • Using thread-specific data
1
2
int pthread_setspecific (pthread_key_t key, const void *value);
void *pthread_getspecific (pthread_key_t key);
  • Pthreads will not call the destructor for a thread-specific data key if the terminating thread has a value of NULL for that key.
  • Using destructor functions
    • If your key's value is a pointer to heap memory, you will need to free the memory to avoid a memory leak each time a thread terminates.Pthreads allows you to define a destructor function when you create a thread-specific data key. When a thread terminates with a non-NULL value for a thread-specific data key, the key's destructor (if any) is called with the current value of the key.
    • the order in which destructors are called is undefined. Try to make each destructor as independent as possible.
  • Scheduling policies and priorities
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    int sched_get_priority_max (int policy);
    int sched_get_priority_min (int policy);
    int pthread_attr_getinheritsched( const pthread_attr_t *attr, int *inheritsched);
    int pthread_attr_setinheritsched( pthread_attr_t *attr, int inheritsched);
    int pthread_attr_getschedparam (const pthread_attr_t *attr, struct sched_param *param);
    int pthread_attr_setschedparam ( pthread_attr_t *attr, const struct sched_param *param);
    int pthread_attr_getschedpolicy (const pthread_attr_t *attr, int *policy);
    int pthread_attr_setschedpolicy (pthread_attr_t *attr, int policy);
    int pthread_getschedparam (pthread_t thread, int *policy, struct sched_param *param);
    int pthread_setschedparam (pthread_t thread; int policy; const struct sched_param *param);
  • When you set the scheduling policy or priority attributes in an attributes object, you must also set the inheritsched attribute!
  • Pthreads does not specify a default value for inheritsched

Priority inversion is when a low-priority thread can prevent a high-priority thread from running

POSIX adjusts to threads

  • fork

    • When a threaded process calls fork to create a child process, Pthreads specifies that only the thread calling fork exists in the child.
    • the thread has the same thread state as in the parent
    • you use fork to clone a threaded program, beware that you may lose access to memory, especially heap memory stored only as thread-specific data values.
    • a mutex was locked at the time of the call to fork, then it is still locked in the child. Because a locked mutex is owned by the thread that locked it, the mutex can be unlocked in the child only if the thread that locked the mutex was the one that called fork. This is important to remember–if another thread has a mutex locked when you call fork, you will lose access to that mutex and any data controlled by that mutex.
  • Avoid using fork inthreaded code except where the child process will immediately exec a new program.

  • Calling pthread_exit from main will terminate the initial thread without affecting the other threads in the process, allowing them to continue and complete normally.

  • flockfile and funlockfile

    1
    2
    3
    void flockfile (FILE *file);
    int ftrylockfile (FILE *file);
    void funlockfile (FILE *file);
  • getchar_unlocked and putchar_unlocked

    1
    2
    3
    4
    int getc_unlocked (FILE *stream);
    int getchar_unlocked (void);
    int putc_unlocked (int c, FILE *stream);
    int putchar_unlocked (int c)

Hints to avoid debugging

  • Never assume that a thread you create will wait for you.
  • Thread inertia is a special case of thread races.
  • thread races that the “loser” generally wins because the memory system will keep the last value written to an address.
  • No ordering exists between threads

  • Scheduling exists to tell the system how important a specific job (thread) is to yourapplication so it can schedule the job you need the most.

  • Synchronization exists to tell the system that no other thread can be allowed into the critical section until the calling thread is done.
  • Cooperate to avoid deadlocks

    One common cause of a deadlock is that some thread has returned from a function without unlocking a mutex

  • Beware of priority inversion
  • Never share condition variables between predicates

    The best solution, when you really want to share a condition variable between predicates, is always to use pthread_cond_broadcast. But when you broadcast, all waiting threads wake up to reevaluate their predicates.

  • Sharing stacks and related memory corrupters

    Having carefully ensured that there is no possible way for the owning thread to pop the stack data while other threads are using the shared data

  • Beware of concurrent serialization
  • Use the right number of mutexes

    A common strategy is to create a separate mutex for each data structure, and use those mutexes to serialize access to the shared data, rather than using the “big mutex” to serialize access to the library.

    • you may decrease the efficiency of the memory system by excessive locking