1、多线程
包含头文件
#include <pthread.h>
编译的时候需要指定库文件 -lpthread
除了信号量,其他的头文件都是这个
查看线程id:返回值为pthread_t类型的线程id,Linux中实际上是unsigned long类型
pthread_t pthread_self(void); // 返回当前线程的线程ID
创建线程
#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
// Compile and link with -pthread, 线程库的名字叫pthread, 全名: libpthread.so libptread.a
- 参数:
thread: 传出参数,是pthread_t类型,线程创建成功,会将线程 ID 写入到这个指针指向
的内存中
attr: 线程的属性,一般情况下使用默认属性即可,写 NULL
start_routine: 函数指针,创建出的子线程的处理动作,也就是该函数在子线程执行,
一般为线程执行函数的函数名即可
arg: 作为实参传递到 start_routine 指针指向的函数内部,多个参数时创建结构体,传入
结构体指针
- 返回值:线程创建成功返回 0,创建失败返回对应的错误号
线程退出:调用该函数当前线程就马上退出了,并且不会影响到其他线程的正常运行
void pthread_exit(void *retval);
- 参数:线程退出的时候携带的数据,当前子线程的主线程会得到该数据。如果不需要使用,指定为 NULL
线程回收:这个函数是一个阻塞函数,阻塞当前线程,等到要回收的线程执行完毕,回收资源
int pthread_join(pthread_t thread, void **retval);
- 参数:
thread: 要被回收的子线程的线程 ID
retval: 二级指针,指向一级指针的地址,是一个传出参数,这个地址中存储了
pthread_exit () 传递出的数据,如果不需要这个参数,可以指定为 NULL
- 返回值:线程回收成功返回 0,回收失败返回错误号。
线程分离:子线程与主线程分离,执行完毕后由操作系统回收资源
int pthread_detach(pthread_t thread);
线程取消:在线程A中杀死线程B,A调用后,B进行了一次系统调用才会被杀死,否则B一直运行
int pthread_cancel(pthread_t thread);
- 参数:要杀死的线程的线程 ID
- 返回值:函数调用成功返回 0,调用失败返回非 0 错误号。
线程比较:也可以直接用=,因为pthread_t本质是unsigned long类型
int pthread_equal(pthread_t t1, pthread_t t2);
- 参数:t1 和 t2 是要比较的线程的线程 ID
- 返回值:如果两个线程 ID 相等返回非 0 值,如果不相等返回 0
2、互斥锁
创建互斥锁:一个临界资源对应一把互斥锁,创建为全局变量,因为需要被多个线程函数访问
pthread_mutex_t mutex;
初始化互斥锁:在主线程中初始化
// 初始化互斥锁
// restrict: 是一个关键字, 用来修饰指针, 只有这个关键字修饰的指针可以访问指向的内存地址, 其他指针是不行的
int pthread_mutex_init(pthread_mutex_t *restrict mutex,
const pthread_mutexattr_t *restrict attr);
- 参数:
mutex: 互斥锁变量的地址
attr: 互斥锁的属性,一般使用默认属性即可,这个参数指定为 NULL
- 返回值
成功返回0,失败返回相应的错误号码
上锁
int pthread_mutex_lock(pthread_mutex_t *mutex);
尝试上锁
int pthread_mutex_trylock(pthread_mutex_t *mutex);
解锁
int pthread_mutex_unlock(pthread_mutex_t *mutex);
销毁互斥锁:一般在主线程结束的时候销毁互斥锁
// 释放互斥锁资源
int pthread_mutex_destroy(pthread_mutex_t *mutex);
3、读写锁
读写锁是互斥锁的升级版,在做读操作的时候可以提高程序的执行效率,如果所有的线程都是做读操作, 那么读是并行的,但是使用互斥锁,读操作也是串行的。
创建读写锁
#include <pthread.h>
pthread_rwlock_t rwlock;
初始化读写锁
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,
const pthread_rwlockattr_t *restrict attr);
读锁
// 在程序中对读写锁加读锁, 锁定的是读操作
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
// 这个函数可以有效的避免死锁
// 如果加读锁失败, 不会阻塞当前线程, 直接返回错误号
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
写锁
// 在程序中对读写锁加写锁, 锁定的是写操作
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
// 这个函数可以有效的避免死锁
// 如果加写锁失败, 不会阻塞当前线程, 直接返回错误号
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
解锁
// 解锁, 不管锁定了读还是写都可用解锁
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
销毁读写锁
// 释放读写锁占用的系统资源
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
4、条件变量
严格意义上来说,条件变量的主要作用不是处理线程同步,而是进行线程的阻塞。如果在多线程程序中只使用条件变量无法实现线程的同步,必须要配合互斥锁来使用。虽然条件变量和互斥锁都能阻塞线程,但是二者是有区别的:
- 如果一个线程抢到互斥锁并加锁成功,其他线程都会被阻塞,无法访问临界资源
- 条件变量是在满足一定的条件下阻塞当前线程,如果条件不满足了,所有线程都可以进入临界区访问临界资源,这种情况下还是会导致数据混乱。
因此,条件变量要和互斥锁配合使用,典型应用为生产者消费者模型。
创建条件变量
#include <pthread.h>
pthread_cond_t cond;
初始化条件变量
// 初始化
int pthread_cond_init(pthread_cond_t *restrict cond,
const pthread_condattr_t *restrict attr);
- 参数:
cond: 条件变量的地址
attr: 条件变量属性,一般使用默认属性,指定为 NULL
线程阻塞函数
// 线程阻塞函数, 哪个线程调用这个函数, 哪个线程就会被阻塞
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
// 表示的时间是从1971.1.1到某个时间点的时间, 总长度使用秒/纳秒表示
struct timespec {
time_t tv_sec; /* Seconds */
long tv_nsec; /* Nanoseconds [0 .. 999999999] */
};
struct timespec tmsp;
tmsp.tv_nsec = 0;
tmsp.tv_sec = time(NULL) + 100; // 线程阻塞100s
// 将线程阻塞一定的时间长度, 时间到达之后, 线程就解除阻塞了
int pthread_cond_timedwait(pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime);
- 满足一定条件时,调用此函数阻塞线程,当线程被阻塞如果线程对互斥锁mutex上锁了,函数内部会把锁打开
- 当被这个函数阻塞的线程被唤醒函数唤醒的时候,如果是所有线程都被唤醒,这些线程会抢这把互斥锁,抢到的线程就会对互斥锁进行上锁,然后进入临界区
唤醒阻塞的线程
// 唤醒阻塞在条件变量上的线程, 至少有一个被解除阻塞
int pthread_cond_signal(pthread_cond_t *cond);
// 唤醒阻塞在条件变量上的线程, 被阻塞的线程全部解除阻塞
int pthread_cond_broadcast(pthread_cond_t *cond);
销毁条件变量
// 销毁释放资源
int pthread_cond_destroy(pthread_cond_t *cond);
5、信号量
头文件为 semaphore.h
创建信号量
#include <semaphore.h>
sem_t sem;
初始化信号量
// 初始化信号量/信号灯
int sem_init(sem_t *sem, int pshared, unsigned int value);
- 参数:
sem:信号量变量地址
pshared: 0:线程同步 非 0:进程同步
value:初始化当前信号量拥有的资源数(>=0),如果资源数为 0,线程就会被阻塞了。
消耗信号量
// 函数被调用sem中的资源就会被消耗1个, 资源数-1
int sem_wait(sem_t *sem);
//不会阻塞
int sem_trywait(sem_t *sem);
//阻塞一定时长,时间结构见pthread_cond_timedwait
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
调用此函数时,如果信号量为0,就阻塞(或者不阻塞)当前线程,当信号量大于0,这个线程被自动唤醒。
增加信号量
// 调用该函数给sem中的资源数+1
int sem_post(sem_t *sem);
如果有线程被这个信号量阻塞了,调用这个函数后该线程被自动唤醒
查看信号量的值
// sval是一个传出参数
int sem_getvalue(sem_t *sem, int *sval);
信号量的值通过sval返回,而不是函数返回值
销毁信号量
int sem_destroy(sem_t *sem);