1.同步的概念
所谓同步,即同时起步,协调一致。不同的对象,对“同步”的理解方式是不同的。如设备同步是指在两个设备间规定一个共同的时间参考;数据库同步是指让两个或多个数据库内容保持一致,或者按需要部分保持一致;文件同步是指让两个或多个文件夹里的文件保持一致等。
1.1 线程同步
同步即协同步调,按规定的先后次序运行。
线程同步,指一个线程发出某一功能调用时,在没有得到结果之前,该调用不返回。同时其他线程保证数据一致性,不能调用该功能。
所有的“多个控制流,共同操作一个共享资源”的情况,都需要同步。
1.2 数据混乱的原因
1.资源共享(独享资源则不会)
2.调度随机(意味着数据访问会出现竞争)
3.线程间缺乏必要的同步机制
以上3点中,前两点不能改变,要提高效率,传递数据,资源必须共享。只要共享资源,就一定会出现竞争。所以数据会容易出现混乱。所以要采取同步机制来避免混乱
2.互斥量mutex
Linux中提供一把互斥锁mutex(称为互斥量)
每个线程在对资源操作前都尝试先加锁,成功加锁才能操作,操作结束解锁
因为资源还是共享的,线程间还是会有竞争,但通过“锁”就将资源的访问变成互斥操作,而后与时间有关的错误也不会再产生了。
2.1 主要应用函数
pthread_mutex_init函数
pthread_mutex_destroy函数
pthread_mutex_lock函数
pthead_mutex_trylock函数
pthread_mutex_unlock函数
以上5个函数的返回值都是:成功返回0,失败返回错误号
pthread_mutex_t类型,其本质是一个结构体。为简化理解,应用时可以忽略其实现细节,简单当成整数看待。
pthread_mutex_t mutex; 变量mutex只有两种取值:1/0(需要多个数值的情况可以使用信号量)
2.1.1 pthread_mutex_init函数
初始化一把互斥锁(互斥量)-- 初值可看作1
int pthread_mutex_init(pthread_mutex_t * restrict mutex, const pthread_mutexattr_t * restrict attr);
参数1:传出参数,调用时传&mutex
restrict关键字:只用于限制指针,告诉编译器,所有修改该指针指向内存中内容的操作,只能通过本指针完成,不能通过除本指针以外的其他变量或指针修改。
参数2:互斥量属性:是一个传入参数,通常传NULL,选用默认属性(线程间共享)
1.静态初始化:如果互斥锁mutex是静态分配的(定义在全局,或加了static关键字修饰)可以直接使用宏进行初始化。
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
2.动态初始化:局部变量应采用动态初始化
pthread_mutex_init(&mutex, NULL);
2.1.2 pthread_mutex_destroy函数
销毁一个互斥锁
int pthread_mutex_destroy(pthread_mutex_t * mutex);
2.1.3 pthread_mutex_lock函数
加锁。可以理解为mute--(或-1)
int pthread_mutex_lock(pthread_mutex_t * mutex);
2.1.4 pthread_mutex_unlock函数
解锁。可以理解为将mutex++(或+1)
int pthread_mutex_unlock(pthread_mutex_t * mutex);
2.1.5 pthread_mutex_trylock函数
尝试加锁
int pthread_mutex_trylock(pthread_mutex_t * mutex);
3.死锁
1.线程试图对同一个互斥量A加锁两次。
2.线程1拥有A锁,请求获得B锁;线程2拥有B锁,请求获得A锁
4.读写锁
与互斥量类似,但读写锁允许更高的并行性。其特性为:写独占,读共享。
4.1 读写锁状态
一把读写锁具备三种状态
1.读模式下加锁状态(读锁)
2.写模式下的加锁状态(写锁)
3.不加锁状态
4.2 读写锁特性
1.读写锁是“写模式加锁”时,解锁前,所有对该锁加锁的线程都会被阻塞
2.读写锁是“读模式加锁”时,如果线程以读模式对其加锁会成功;如果线程以写模式加锁会阻塞
3.读写锁是“读模式加锁”时,既有试图以写模式加锁的线程,也有试图以读模式加锁的线程。那么读写锁会阻塞随后的读模式锁请求。优先满足写模式锁。
即:读锁、写锁并行阻塞,写锁优先级高
读写锁也叫共享-独占锁。当读写锁以读模式锁住时,它是以共享模式锁住的;当它以写模式锁住时,它是以独占模式锁住的。写独占,读共享
读写锁非常适合于对数据结构读的次数远大于写的情况
4.3 主要应用函数
4.3.1 pthread_rwlock_init函数
初始化读写锁
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,
const pthread_rwlockattr_t *restrict attr);
还可以静态初始化
pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;
4.3.2 pthread_rwlock_destroy函数
销毁读写锁
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
4.3.3 pthread_rwlock_rdlock函数
以读方式加锁
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
4.3.4 pthread_rwlock_wrlock函数
以写方式加锁
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
4.3.5 pthread_rwlock_tryrdlock函数
尝试以读方式加锁,不阻塞
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
4.3.6 pthread_rwlock_trywrlock函数
尝试以写方式加锁,不阻塞
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
4.3.7 pthread_rwlock_unlock函数
解锁
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
以上7个函数的返回值是:成功返回0,失败直接返回错误号
5.条件变量
条件变量本身不是锁,但它可以造成线程阻塞。通常与互斥锁配合使用,给多线程提供一个会和的场所。
5.1 主要应用函数
5.1.1 pthread_cond_init函数
初始化一个条件变量
int pthread_cond_init(pthread_cond_t *restrict cond,
const pthread_condattr_t *restrict attr);
静态初始化
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
5.1.2 pthread_cond_destroy函数
销毁一个条件变量
int pthread_cond_destroy(pthread_cond_t *cond);
5.1.3 pthread_cond_wait函数
阻塞等待一个条件变量
int pthread_cond_wait(pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mutex);
函数作用:
1.阻塞等待条件变量cond (参数1) 满足
2.释放已掌握的互斥锁(解锁互斥量)相当于
pthread_mutex_unlock(&mutex);
1 2 两步为一个原子操作
3.当被唤醒,pthread_cond_wait函数返回时,解除阻塞并重新申请获取互斥锁pthread_mutex_lock(&mutex);
5.1.4 pthread_cond_timedwait函数
限时等待一个条件变量
int pthread_cond_timedwait(pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mutex,
const struct timespec *restrict abstime);
用法
pthread_cond_t cond;// 定义条件变量
pthread_cond_init(&cond); // 初始化条件变量
time_t cur = time(NULL); //获取当前时间
struct timespec t; //定义timespec结构体变量t
t.tv_sec = cur + 1; // 定时1秒
pthread_cond_timedwait(&cond, &mutex, &t); // 调用
5.1.5 pthread_cond_signal函数
唤醒至少一个阻塞在条件变量上的线程
int pthread_cond_signal(pthread_cond_t *cond);
5.1.6 pthread_cond_broadcast函数
唤醒所有阻塞在条件变量上的线程
int pthread_cond_broadcast(pthread_cond_t *cond);
以上6个函数的返回值都是:成功返回0,失败直接返回错误号
pthread_cond_t类型 用于定义条件变量
如pthread_cond_t cond;
5.2 条件变量的优点
相较于mutex而言,条件变量可以减少竞争。
如果直接使用mutex,除了生产者和消费者之间要竞争互斥量外,消费者之间也需要竞争互斥量,但如果产品队列中没有生产出产品,那么消费者之间竞争互斥锁是没有意义的。有了条件变量机制后,只有生成者完成生产,才会引起消费者之间的竞争,提高了程序的效率。
6.信号量
进化版的互斥锁(1-- N)
由于互斥锁的粒度比较大,如果我们希望在多个线程间对某一对象的部分数据进行共享,使用互斥锁是没有办法实现的,只能将整个数据对象锁住。这样虽然达到了多线程操作共享数据时保证数据正确性的目的,却是无形中导致线程的并发性下降。线程从并行执行,变成了串行执行。与直觉使用单进程无异。
信号量,是相对折中的一种处理方式,既能保证同步数据不混乱,又能提高线程并发
6.1信号量的基本操作
头文件为:#include <semaphore.h>
sem_wait: 1.信号量大于0,则信号量 --
2.信号量等于0,造成线程阻塞
sem_post: 将信号量++,同时唤醒阻塞在信号量上的线程
由于sem_t的实现对用户隐藏,所以信号量的 ++, -- 操作只能通过函数来实现,而不能直接用 ++,-- 符号。
信号量的初值,决定了占用信号量的线程的个数
信号量不限用于线程间同步,还能用于进程间同步
6.2信号量的主要函数
6.2.1 sem_init函数
初始化一个信号量
int sem_init(sem_t *sem, int pshared, unsigned int value);
参数1: sem信号量
参数2: pshared取0用于线程间,取非0用于进程间
参数3:value指定信号量初值
6.2.2 sem_destroy函数
销毁信号量
int sem_destroy(sem_t *sem);
6.2.3 sem_wait函数
给信号量加锁 (-- 操作)
int sem_wait(sem_t *sem);
6.2.4 sem_post函数
给信号量解锁(++操作)
int sem_post(sem_t *sem);
6.2.5 sem_trywait函数
尝试给信号量加锁 (-- 操作), 非阻塞
int sem_trywait(sem_t *sem);
6.2.6 sem_timedwait函数
限时内尝试给信号量加锁 (-- 操作)
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
Comments