PThread (POSIX Thread)

All threads share the same global memory, including:

  • data segment: initialized data and uninitialized data(BSS)
  • heap segment

But per-thread statks not shared.

On Linux, threads are implemented using clone(), and its ten times faster than fork(). Even though fork() has COW mechanism, thread do not need copy many of the attributes such as page tables.(线程是调度的基本单位,进程是资源分配的基本单位)

Two main POSIX Threading implementations on Linux:

  • Linux Threads
  • Native POSIX Threads Library (NPTL)

1. errno

On Linux, each thread has their own errno value. The traditional method of returning status from system calls and some library functions is to return 0 on success and –1 on error, with errno being set to indicate the error. But all Pthreads functions return 0 on success and return position value on failure. The failure value is same as errno and can be placed in errno by traditional UNIX system call.

2. compile

Use -pthread flags for gcc. The effects of this option including following:

  • _REENTRANT preprocessor macro is defined. This causes the declarations of a few reentrant functions to be exposed.
  • The program is linked with the libpthread library (the equivalent of –lpthread ).

3. Thread Control

  • pthread_create
  • pthread_exit
  • pthread_self
  • pthread_equal
  • pthread_join
  • pthread_detach

4. synchronize

4.1 mutex (mutual exclusion)

A mutex is a variable of the type pthread_mutex_t.

  • statically initialize: pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
  • dynamically initialize: pthread_mutex_t mtx = pthread_mutex_init();
    • PTHREAD_MUTEX_DEFAULT/NULL: same as PTHREAD_MUTEX_NORMAL on Linux
    • PTHREAD_MUTEX_NORMAL: may cause deadlock
    • PTHREAD_MUTEX_ERRORCHECK: check deadlock
    • PTHREAD_MUTEX_RECURSIVE: has lock count
  • locking a mutex: int pthread_mutex_lock(pthread_mutex_t *mutex);
    • pthread_mutex_trylock()
    • pthread_mutex_timedlock()
  • unlocking a mutex: int pthread_mutex_unlock(pthread_mutex_t *mutex);
  • destroy a mutex: pthread_mutex_destroy(), only for non-static mutex

If a thread lock the mutex that it has locked by thread self again, may result: the thread deadlocks, blocked trying to lock a mutex that it already owns, or the call fails, returning the error EDEADLK. On Linux, the thread deadlocks by default.(lock不可重入?)

mutex locking is advisory, rather than mandatory(和Linux下文件锁机制相同).

Some performance data:

  • without mutexes: 0.35s
  • with mutexes: 3.1s
  • with file lock: 22s
  • with System V semaphore: 14s

On Linux, mutexes are implemented using futexes (an acronym derived from fast user space mutexes ), and lock contentions are dealt with using the futex() system call.

To avoid deadlock, when threads can lock the same set of mutexes, they should always lock them in the same order.

4.1.1 POSIX semaphores versus Pthreads mutexes

POSIX semaphores and Pthreads mutexes can both be used to synchronize the actions of threads within the same process, and their performance is similar. How-ever, mutexes are usually preferable, beca use the ownership property of mutexes enforces good structuring of code (only the thread that locks a mutex can unlock it). By contrast, one thread can increment a semaphore that was decremented by another thread. This flexibility can lead to poorly structured sy nchronization designs. (For this reason, semaphores are sometime s referred to as the “gotos” of concurrent programming.)

There is one circumstance in which mutexes can’t be used in a multithreaded application and semaphores may therefore be preferable. Because it is async-signal-safe (see Table 21-1, on page 426), the sem_post() function can be used from within a signal handler to synchronize with another thread. This is not possible with mutexes, because the Pthreads functions for operating on mutexes are not async-signal-safe. However, because it is usually preferable to deal with asynchronous sig-nals by accepting them using sigwaitinfo() (or similar), rather than using signal han-dlers (see Section 33.2.4), this advantage of semaphores over mutexes is seldom required.

4.2 condition variable

A condition variable allows one thread to inform other threads about changes in the state of a shared variable (or other shared resource) and allows the other threads to wait (block) for such notification.

The principal condition variable operations are signal and wait. The signal opera-tion is a notification to one or more waiting threads that a shared variable’s state has changed. The wait operation is the means of blocking until such a notification is received.

A condition variable has the type pthread_cond_t.

  • statically initialize: pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
  • dynamically initialize: pthread_cond_t cond = pthread_cond_init();
  • singal:
    • pthread_cond_signal(): signal used to indicate source availability
    • pthread_cond_broadcast(): broadcast used to indicate state change
  • wait:
    • pthread_cond_wait()
    • pthread_cond_timedwait()
  • destroy: pthread_cond_destroy(), only for non-static cond

The wait function do these steps(一定先释放lock):

  1. unlock the mutex specified by mutex;
  2. block the calling thread until another thread signals the condition variablecond ;
  3. relock mutex.
4.2.1 Spurious wakeup problem

Spurious wakeup is that a thread might be awoken from its waiting state even though no thread signaled the condition variable[^SWP]. Because spurious wakeup can happen repeatedly, this problem can be solved by waiting inside a loop that terminates when the condition is true:

1
2
3
4
5
6
7
8
9
10
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

// wait thread 
while(...check wait object..) {
	pthread_cond_wait(&cond);
}

// signal thread
...set wait object...
pthread_cond_signal(&cond);

Reference