Inter-Thread Communication
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 linklibpthread
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 formUsually, 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 byvoid**
. Now, you may wonder, if a thread has already exited beforepthread_join()
is called, where wouldstatus
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
asNULL
.
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?
- Semaphore can also be used for synchronization, while mutex can only be used for mutual exclusion.
- 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