LinuxC/C++ 线程、锁和条件变量
线程
线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位. 一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务,它们共享该进程所拥有的资源.
Linux下线程默认栈的大小为8M,可以使用ulimit -s
命令查看:
创建线程
首先需要手动链接pthread.h
头文件.
如果使用CMake,需要在CMakeLists.txt
文件最后加上:
# first是编译后二进制文件名
TARGET_LINK_LIBRARIES(first pthread)
pthread.h
直接提供了创建线程的API:
#include <pthread.h>
// 成功返回0,失败返回错误编号
int pthread_create(pthread_t *restrict tidp,
const pthread_attr_t *restrict attr,
void *(start_rtn)(void *),
void *restrict arg);
- tidp:线程Id.
- attr:用于定制线程的属性,默认则置为NULL.
- start_rtn:线程创建成功后运行的回调函数.
- arg:传进start_rtn函数的参数.
线程要执行的回调函数:
void *thread_callback(void *arg) {
// TODO
}
终止线程
#include <pthread.h>
void pthread_exit(void *rval_ptr);
- retval表示线程退出状态,通常传NULL.
用于强制退出线程(还未执行完毕).
阻塞线程
#include <pthread.h>
//
int pthread_join(pthread thread, void **rval_ptr);
- pthread_t thread: 待阻塞线程的线程号.
- void **retval : 指向一个指向阻塞线程的返回码的指针的指针,一般传NULL.
pthread_join()
主要有两种作用:
- 用于等待其他线程结束:当调用 pthread_join() 时,当前线程会处于阻塞状态,直到被调用的线程结束后,当前线程才会重新开始执行.
- 对线程的资源进行回收:如果一个线程是非分离的(默认情况下创建的线程都是非分离)并且没有对该线程使用
pthread_join()
的话,该线程结束后并不会释放其内存空间,这会导致该线程变成了“僵尸线程”.
锁
互斥锁
并发环境下为了保证线程安全,可以对临界资源加上锁,确保同一时间只有一个线程访问数据. 互斥锁加锁失败后,线程释放CPU,给其他线程.
初始化锁:
#include <pthread>
int pthread_mutex_init(pthread_mutex_t *restrict mutex,
const pthread_mutexattr_t *restrict attr);
- mutex:待初始化的锁.
- attr:锁的属性,默认则设置为NULL.
加锁和解锁:
#include <pthread>
int pthread_mutex_lock(pthread_mutex_t * mutex);
int pthread_mutex_unlock(pthread_mutex_t * mutex);
自旋锁
自旋锁加锁失败后,线程会忙等待,直到它拿到锁.
初始化锁:
#include <pthread>
int pthread_spin_init(pthread_spinlock_t *lock,
int pshared);
加锁和解锁:
#include <pthread>
int pthread_spin_lock(pthread_spinlock_t *lock);
int pthread_spin_unlock(pthread_spinlock_t *lock);
使用互斥锁还是自旋锁主要看临界区的工作量怎么样,如果工作量小(付出的代价小于线程切换上下文)可以使用自旋锁,如果工作量大(付出的代价大于线程切换上下文)则可以选择使用互斥锁.
条件变量
上面提到的pthread_join()
函数可以阻塞当前的线程,等待子线程执行完毕,实际上是一种线程同步的机制,而条件变量则是线程同步的另外一种机制.
条件变量一般与互斥锁一起使用,互斥锁只有两种状态:锁定和非锁定,而条件变量可以通过线程阻塞和等待另一个线程发送信号, 从而弥补互斥锁的不足.
初始化条件变量
int pthread_cond_init(pthread_cond_t *restrict cond,
const pthread_condattr_t *restrict attr);
让当前线程阻塞等待
int pthread_cond_wait(pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mutex);
当前线程会进入等待队列,然后进入休眠,等待唤醒.
唤醒至少一个阻塞在条件变量上的线程
int pthread_cond_signal(pthread_cond_t *cond);
唤醒所有阻塞在条件变量上的线程
int pthread_cond_broadcast(pthread_cond_t *cond);
条件变量的使用
A线程:
pthread_mutex_lock(&mutex);
while (false == ready) {
pthread_cond_wait(&cond, &mutex);
}
pthread_mutex_unlock(&mutex);
B线程:
pthread_mutex_lock(&mutex);
ready = true;
pthread_mutex_unlock(&mutex);
pthread_cond_signal(&cond);
一共会出现两种情况:
- 如果 Thread A 先拿到 mutex,那么此时 ready 为 false,Thread A 调用
pthread_cond_wait
进入等待队列,接着释放 mutex,然后 Thread B 才能修改 ready,并 signal. - 如果 Thread A 没有拿到 mutex,Thread B 拿到 mutex,然后修改 ready 为 true,然后释放锁,这样 Thread A 在拿到 mutex,就不会再进 while 循环调 wait 了.