System Programming

" One vision, one purpose. "

Copyright © Tony's Studio 2020 - 2022


Chapter Eight - Inter Thread Communication

8.1 Meet Thread

8.1.1 What Is Thread?

线程是在共享内存空间中并发的多道执行路径,它们共享一个进程的资源

Linux 线程属于用户级线程,即线程的调度是在用户空间执行的。Linux 线程遵循 POSIX 线程接口,称为pthread,在其他平台也有对应的实现.。

线程是最小的调度单位,进程是最小的资源分配单位

8.1.2 Concurrency and Parallelism

Concurrency: 并发是指在一段时间宏观上有多个程序在同时运行,但在单处理机系统中,每一时刻却仅能只有一道程序在执行,故微观上这些程序只能是分时交替执行

Parallelism: 并行的确是多个程序同时运行。比如一个“6 核 6 线程”的芯片,那他就支持 6 个线程并行(1个核心对应1个线程)。而超线程技术可以做到线程属大于核心数,比如“4 核 8 线程”。

8.1.3 Get Ready for Thread

To play with thread, you need pthread.h. And when you do the linking, you have to link libpthread manually.

1
gcc thread.c -lpthread -o thread

8.2 Play with Thread

8.2.1 Create Thread

It is easy to create a thread. And thread task has a fixed form. Once called, the thread will start directly on success.

1
2
3
4
5
6
int pthread_create(pthread_t *thread,
const pthread_attr_t *attr,
void *(*start_routine)(void *arg),
void *arg);

void *task(void *arg); // thread task form

Usually, if no arguments, we can just write like this. If we have arguments, similar to function arguments, it should better not be a local variable, which might be destroyed unintentionally. Use malloc or global variables instead.

1
pthread_create(&thread, NULL, task, NULL);

A thread has thread id, too. We can get it by pthread_self().

1
int pthread_self(void);

8.2.2 Exit Thread

A thread is not a process, who can only return integer. It can return various values. So void* is the best choice, and we do not afraid of it at all!

1
void pthread_exit(void *status);

Here, the same thing. Do not return a local variable’s address. Either use malloc or global variable.

8.2.3 Wait for Thread

Well, A parent thread may want to wait for a child thread, and get its return value. Here, we can use pthread_wait(). This will stops current thread until the desired thread exits.

1
int pthread_join(pthread_t thread, void **retval);

The return value of a thread is void*, so we get it by void**. Now, you may wonder, if a thread has already exited before pthread_join() is called, where would status be stored? Well, when a thread exits, it won’t be cleaned up, ya know, just like what is in process. But again, return value must not be a local variable.

8.2.3 Detach Thread

If we do not want to wait and manage a thread, we can simply detach it. But once detached, it can never be retrieved, and will not be able to get its return value.

1
int pthread_detach(pthread_t thread);

8.3 Synchronization and Mutual Exclusion

For threads, synchronization and mutual exclusion are still big problems. Here, we got three ways to deal with them, semaphore, mutex and condition variable.

8.3.1 Semaphore

We’ve met semaphore before. Here, it is basically the same, but much more convenient. it requires semaphore.h. These functions all have regular return behavior.

8.3.1.1 Initialize Semaphore

Unlike what we’ve done in process, it is much easier to create semaphore among threads, just a global variable to make it visible to all threads using it.

1
int sem_init(sem_t *sem, int pshared, unsigned int value);

pshared is usually 0. If not, it means the semaphore is shared among processes, and must be stored in shared memory. For now, leave it 0.

value is still the value, huh. 0 for synchronization and 1 or more for mutual exclusion.

8.3.1.2 P/V Operation

Well, just what semaphore functions, huh? Here, these two are already wrapped for us.

1
2
int sem_wait(sem_t *sem);	// P operation
int sem_post(sem_t *sem); // V operation

8.3.1.3 Destroy Semaphore

It’s a good habit to recycle things.

1
int sem_destroy(sem_t *sem);

8.3.2 Mutex

Mutex, mutex, mutex, mut… ex…, mutual exclusion?! Seriously?

Code area locked by mutex can only be accessed by one thread at a time.

8.3.2.1 Initialize Mutex

Usually, we can leave mutexattr as NULL.

1
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr);

Well, there is something about mutexattr.

mutexattr meaning
PTHREAD_MUTEX_TIMED_NP 缺省值,即普通锁。当一个线程加锁以后,其余请求锁的线程将形成一个等待队列,并在解锁后按优先级获得锁,这种锁策略保证了资源分配的公平性。
PTHREAD_MUTEX_RECURSIVE_NP 嵌套锁,允许同线程内对同一个锁加锁多次,记录加锁次数,并允许通过多次 unlock 解锁。如果是不同线程请求,则在加锁线程解锁时重新竞争。
PTHREAD_MUTEX_ERRORCHECK_NP 检错锁,禁止同线程内对同一个锁加锁多次,如果同一个线程请求同一个锁,则返回 EDEADLK,否则与PTHREAD_MUTEX_TIMED_NP 类型动作相同。这样就保证当不允许多次加锁时不会出现最简单情况下的死锁。
PTHREAD_MUTEX_ADAPTIVE_NP 适应锁,效率更高,等同于多次 trylock() + PTHREAD_MUTEX_TIMED_NP

If mutex is static, we can use a macro to initialize it.

1
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

8.3.2.2 Use Mutex

Well, mutex is like a lock, all threads compete to lock the mutex, and only one can succeed. When unlocked, another competition starts.

1
2
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);

8.3.2.3 Destroy Mutex

Mutex should be destroyed after use.

1
int pthread_mutex_destroy(pthread_mutex_t *mutex);

8.3.2.4 More Mutex

There are many types of mutex, like spin lock, try lock, whatever… Just similar, but have different application and efficiency. For example, spin lock can be used to guard small region.

1
2
3
4
int pthread_spin_init(pthread_spinlock_t *spinlock);
int pthread_spin_lock(pthread_spinlock_t *spinlock);
int pthread_spin_unlock(pthread_spinlock_t *spinlock);
int pthread_spin_destroy(pthread_spinlock_t *spinlock);

8.3.2.5 Mutex vs Semaphore

It seems that semaphore can do what mutex does? So what’s the difference?

  1. Semaphore can also be used for synchronization, while mutex can only be used for mutual exclusion.
  2. Mutex can only be 0 or 1, while semaphore can be non-negative.

8.2.3 Condition Variable

Unlike mutex, condition variable is used to wait, instead of lock. It is used to block a thread automatically, and wait until certain condition is met. However, it needs the help of mutex to do this. Still, these functions have regular return behavior.

8.2.3.1 Initialize Condition Variable

Well, still not that difficult to use condition variable. Since it needs the help of mutex, there should also be a mutex be initialized, too.

1
int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);	

Similarly, if a condition variable is static, we can initialize it by a macro, too.

1
static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

8.2.3.2 Wait for Condition

Condition variable is tend to wait, so just wait? It can only wait when current thread holds the mutex. Then, it will hang current thread up, and unlock the mutex for other threads to run. If condition is met, and mutex is unlocked again by other threads, it still needs to compete over the mutex.

1
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);

8.2.3.3 Break Condition

Once a thread is put to wait for a condition, it needs other threads to wake it up by breaking the condition. Hmm… Just call them? Give them a signal? Or broadcast the big news?

1
2
int pthread_cond_signal(pthread_cond_t *cond);	  // wake up at least one
int pthread_cond_broadcast(pthread_cond_t *cond); // wake up all

" Do or do not. There is no try. "

Copyright © Tony's Studio 2020 - 2022

- EOF -