3.1信号量
信号量:是一个特殊类型的变量,这个信号量可以被增加或者减少,当信号量大于0时,代表资源可以被访问。当访问完后,信号量减1。当信号量为0时,要想访问信号量就要等待(阻塞)。根据信号量的值可以分为:二进制信号量和计数信号量
信号量的创建/销毁:
#include <semaphore.h> int sem_init(sem_t *sem, int pshared, unsigned int value); int sem_destroy(sem_t *sem);
- sem:信号量
- pshared:0:线程同步 1:进程同步
- value:信号量初始值
- 返回值:成功返回0,错误返回错误码
信号量的访问/释放:
#include <semaphore.h> int sem_wait(sem_t *sem); int sem_post(sem_t *sem);
wait会对信号量减1,如果信号量大于0,就直接减1。如果信号量等于0,就会等待。
post会对信号量加1。
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <pthread.h> #include <semaphore.h> void *thread_function(void *arg); sem_t g_sem; int main() { int ret; pthread_attr_t thread_attr; pthread_t thread; ret = pthread_attr_init(&thread_attr); if(ret != 0) { perror("ptherad attribute init failed"); exit(EXIT_FAILURE); } ret = sem_init(&g_sem, 0, 0); if(ret != 0) { perror("semaphore init failed"); exit(EXIT_FAILURE); } ret = pthread_attr_setdetachstate(&thread_attr, PTHREAD_CREATE_DETACHED); if(ret != 0) { perror("setting thread detached state failure"); exit(EXIT_FAILURE); } ret = pthread_create(&thread, &thread_attr, thread_function, NULL); if(ret != 0) { perror("thread create failed"); exit(EXIT_FAILURE); } ret = pthread_attr_destroy(&thread_attr); if(ret !=0) { perror("thread attr destory failed"); exit(EXIT_FAILURE); } printf("ready to wait sem \n"); sem_wait(&g_sem); sleep(2); printf("the main thread is finish \n"); exit(EXIT_SUCCESS); } void *thread_function(void *arg) { sleep(3); printf("finish thread function \n"); sem_post(&g_sem); pthread_exit(NULL); }
互斥量
互斥量(mutex)从本质上说是一把锁,在访问共享资源前对互斥量进行设置(加锁),在访问完成后会释放(解锁)互斥量。
#include<pthread.h> int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr); int pthread_mutex_destroy(pthread_mutex_t *mutex); 返回值: 成功:0; 失败:返回错误编号 attr: PTHREAD_MUTEX_INITIALIZER创建快速互斥锁
PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP创建递归互斥锁
PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP创建检错互斥锁
要用默认的属性初始化互斥锁(快速锁),只需把attr设置为NULL。
这三种锁的区别:主要在于其他未占有互斥锁的线程在希望得到互斥锁时是否需要阻塞等待。
快速锁:是指调用线程会阻塞直至拥有互斥锁的线程解锁为止。
递归互斥锁:能够成功地返回并且增加调用线程在互斥上加锁的次数。
检错互斥锁:则为快速互斥锁的非阻塞版本,它会立即返回并返回一个错误信息。
三种锁分别适用于哪些场景?后期需要补充。
#include <pthread.h> int pthread_mutex_lock(pthread_mutex_t *mutex); int pthread_mutex_trylock(pthread_mutex_t *mutex); int pthread_mutex_unlock(pthread_mutex_t *mutex); 返回值:成功:0; 失败:错误编号
线程调用pthread_mutex_lock时候,如果互斥量已经上锁,则线程会被阻塞直到互斥量解锁。
如果不希望线程阻塞,可以调用pthread_mutex_unlock。如果互斥量已经上锁,会立即返回EBUSY。
#include <pthread.h> #include <time.h> int pthread_mutex_timedlock(pthread_mutex_t *restrict mutex, const struct timespec *restrict tsptr); 返回值:成功:0;失败:错误编号
这里的时间是绝对时间,而不是相对时间。后面补充一个使用这个函数的例子
避免死锁
死锁(deallocks): 是指两个或两个以上的进程(线程)在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或 系统产生了死锁,这些永远在互相等待的进程(线程)称为死锁进程(线程)。 由于资源占用是互斥的,当某个进程提出申请资源后,使得有关进程(线程)在无外力协助下,永远分配不到必需的资源而无法继续运行,这就产生了一种特殊现象 死锁。
http://blog.51cto.com/ab3813/1765422 这里对死锁进行了详细的说明。后面再详细学习。
银行家算法
1.当使用的是快速锁的时候,也就是默认锁。如果程序试图对一个已经加了锁的互斥量调用pthread_mutex_lock,程序就会被阻塞,而又因为拥有互斥量的这个线程正是现在被阻塞的线程,所以互斥量就永远也不会解锁了。就是一个线程出现多重加锁的情况。
解决方法:可以通过改变互斥量的属性来解决。用递归或检错互斥锁。
读写锁
读写有三种状态:读模式下加锁状态,写模式下加锁状态,不加锁状态。而且一次只有一个线程可以占有写模式的读写锁,但是多个线程可以同时占有读模式的读写锁。
与互斥量相比的优点:
当读写锁是写加锁状态时,在这个锁被解锁之前,所有试图对这个锁加锁的线程都会被阻塞。当读写锁在读加锁状态时,所有试图以读模式对它进行加锁的线程都可以得到访问权。但是任何希望以写模式对此锁进行加锁的线程都会阻塞,直到所有的线程释放它们的读锁为止。但当读写锁处于读模式锁住的状态,而这时有一个线程试图以写模式获取锁时,读写锁通常会阻塞随后的读模式锁请求。这样可以避免读模式锁长期占用,而等待的写模式请求一直得不到满足。
读写锁非常适合用于对数据结构读的次数远大于写的情况。
#include <pthread.h> int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr); int pthread_rwlock_destroy(pthread_rwlock_t *rwlock); 返回值: 成功:0; 失败:错误编号
#include <pthread.h> int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock); int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock); int pthread_rwlock_unlock(pthread_rwlock_t *rwlock); 返回值: 成功:0;失败:错误编号
#include <pthread.h> int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock); int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock); 返回值: 成功:0;失败:错误编号
带有超时的读写锁
#include <pthread.h> #include <time.h> int pthread_rwlock_timedrdlock(pthread_rwlock_t *restrict rwlock, const struct timespec *restrict tsptr); int pthread_rwlock_timedwrlock(pthread_rwlock_t restrict rwlock, const struct timespec *restrict tsptr); 返回值:成功:0;失败:错误编号
tsptr参数指向timespec结构 指定线程应该停止阻塞的时间。
注意:超时指定的是绝对时间,而不是相对时间。
条件变量
条件变量的初始化:由pthread_cond_t数据类型表示的条件变量可以用两种方式进行初始化,可以用常量PTHREAD_COND_INITIALIZER赋给静态分配的条件变量。也可以动态的使用pthread_cond_init函数对它进行初始化。
#include <pthread.h> int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict arrt); int pthread_cond_destroy(pthread_cond_t *cond); 返回值: 成功:0;错误:错误编号
使用默认属性的时候,将arrt设置为NULL。
尽管POSIX标准中为条件变量定义了属性,但在LinuxThreads中没有实现,因此cond_attr
值通常为NULL,且被忽略。
pthread_cond_destroy只有在没有线程在该条件变量上等待的时候才能注销这个条件变量,否则返回EBUSY。因为Linux实现的条件变量没有分配什么资源,所以注销动作只包括检查是否有等待线程。
#include <pthread.h> int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex); int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict tspir); 返回值: 成功:0; 失败:错误编号
time的时间是绝对时间,不是相对时间
#include <pthread.h> int pthread_cond_signal(pthread_cond_t *cond); int pthread_cond_broadcast(pthread_cond_t *cond); 返回值: 成功:0;失败:错误编号
pthread_cond_signal函数至少能唤醒一个等待该条件的线程,而pthread_cond_broadcast函数则能唤醒等待该条件的所有线程。需要注意的是:一定要在改变条件状态以后再给线程发信号。
条件变量的使用可以分为两部分:
等待线程
使用pthread_cond_wait前要先加锁;
pthread_cond_wait内部会解锁,然后等待条件变量被其它线程激活;
pthread_cond_wait被激活后会再自动加锁;
激活线程:
加锁(和等待线程用同一个锁);
pthread_cond_signal发送信号;
解锁;
应用实例:https://www.cnblogs.com/studystudent/p/3333882.html
自旋锁
自旋锁主要用于锁持有时间短,而且线程不希望在重新调度上花费太多的成本。但是由于现代处理器的进步,上下文切换越来越快,所以自旋锁只在某些特定的情况下有用。