【UNIX环境高级编程】线程同步
当多个线程共享相同的内存时,需要确保每个线程看到一致的数据视图。如果每个线程使用的变量都是其他线程不会读取和修改的,那么就不存在一致性问题。同样,如果变量是只读的也不会有一致性问题。但是,当一个线程可以修改变量,其他线程也可以读取或者修改的时候,我们就需要对这些线程进行同步,确保它们在访问变量的存储内容时不会访到无效的值。
互斥量
可以使用pthread的互斥接口来保护数据,确保同一时间只有一个线程访问数据。互斥量(mutex)从本质上说是一把锁,在访问共享资源前对互斥量进行设置(加锁),在访问完成后释放(解锁)互斥量。对互斥量进行加锁以后,任何其他试图再次对互斥量加锁的线程都会被阻塞直到当前 线程释放该互斥锁。如果释放互斥量时有一个以上的线程阻塞,那么所有该锁上的阻塞线程都会变为可运行状态,第一变为可运行状态的线程就可以对互斥量加锁,其他线程就会看到互斥量亦然是锁着的,只能回去再次等待它重新变为可用。在这种方式下,每次只有一个线程可以向前执行。
读写锁
读写锁(read-write lock)与互斥量类似,不过读写锁允许更高程度的并行性。互斥量要么是锁住状态,要么就是不加锁状态,而且一次只有一个线程可以对其进行加锁。读写锁有3种状态:读模式下加锁,写模式下加锁,不加锁。一次只有一个线程可以占用写模式的读写锁,但是多个线程可以同时占用读模式的读写锁。
当读写锁是写加锁状态时,在这个锁被解锁之前,所有试图对这个锁加锁的线程都会被阻塞。当读写锁在读加锁状态时,所有试图以读模式对它进行加锁的线程都可以得到访问权,但是任何希望以写模式对此进行加锁的线程都会阻塞,只有所有的线程释放它们的读锁为止。虽然各种操作系统对读写锁的实现各不相同,但当读写锁处于读模式锁住的状态,而这时有一个线程试图以写模式获取锁时,读写锁通常会阻塞随后的读模式锁请求。这样就可以避免读模式长期占用,而等待的写模式锁请求一直得不到满足。
读写锁非常适合于对数据结构读的次数远大于写的情况。当读写锁在写模式下时,它所保护的数据结构就可以被安全地修改,因此一次只有一个线程可以在写模式下拥有这个锁。当读写锁在读模式下时,只要线程先获取了读模式下的锁,该锁保护的数据结构就可以被多个获得读模式锁的线程读取。
条件变量
条件变量是线程可用的另一种同步机制。条件变量给多个线程提供了一个会和的场所。条件变量与互斥量一起使用时,允许线程以无竞争的方式等待特定的条件发生。条件本身是由互斥量保护的。线程在改变条件状态之前必须首先锁住互斥量。其他线程在获得互斥量之前不会察觉到这种改变,因为互斥量必须在锁住之后才能计算条件。
下面是用条件变量解决生产者-消费者问题:
#include<stdio.h>
#include<pthread.h>
#define MAX 10000
pthread_mutex_t the_mutex;
pthread_cond_t condc, condp; int buffer = ; void *Producer(void *ptr) {
int i; for (int i = ; i <= MAX; i++) {
pthread_mutex_lock(&the_mutex);
while (buffer != ) pthread_cond_wait(&condp, &the_mutex);
buffer = i;
pthread_cond_signal(&condc);
pthread_mutex_unlock(&the_mutex);
}
pthread_exit();
} void *Consumer(void *ptr) {
int i;
for (int i =; i < MAX; i++) {
pthread_mutex_lock(&the_mutex);
while (buffer == ) pthread_cond_wait(&condc, &the_mutex);
buffer = ;
pthread_cond_signal(&condp);
pthread_mutex_unlock(&the_mutex);
}
pthread_exit();
} int main(int argc, char **argv)
{
pthread_t pro, con;
pthread_mutex_init(&the_mutex, );
pthread_cond_init(&condc, );
pthread_cond_init(&condc, );
pthread_create(&con, , Consumer, );
pthread_create(&pro, , Producer, );
pthread_join(pro, );
pthread_join(con, );
pthread_cond_destroy(&condc);
pthread_cond_destroy(&condp);
pthread_mutex_destroy(&the_mutex); }
自旋锁
自旋锁与互斥量类似,但它不是通过休眠使进程阻塞,而是在获取锁之前一直处于忙等(自旋)状态。自旋锁可用于以下情况:锁被持有的时间短,而且线程并不希望在重新调度上花费太多的成本。
自旋锁通常作为底层原语用于实现其他类型的锁。根据它们所基于的系统体系结构,可以通过使用测试并设置指令有效地实现。当然这里说的有效也还是会导致CPU资源的浪费:当线程自旋等待锁变为可用时,CPU不能做其他的事情。这也是自旋锁只能够被位置一小段时间的原因。
自旋锁用在非抢占式内核中时是非常有用的:除了提供互斥机制以外,它们会阻塞中断,这样中断处理程序就不会让系统陷入死锁状态,因为它需要获取已被加锁的自旋锁(把中断想成另一种抢占)。在这种类型的内核中,中断处理程序不能休眠,因为它们能用的同步原语只能是自旋锁。
屏障
屏障(barrier)是用户协调多个线程并行工作的同步机制。屏障允许每个线程等待,直到所有的合作线程都到达某一点,然后从该点继续执行。我们已经看到一种屏障,pthread_join函数是一种屏障,允许一个线程等待,直到另一个线程退出。
但是屏障对象的概念更广,它们允许任意数量的线程等待,直到所有的线程处理完工作,而线程不需要退出。所有线程到达屏障后可以直接工作。
互斥锁和自旋锁的区别:
自旋锁是一种非阻塞锁,也就是说,如果某线程需要获取自旋锁,但该锁已经被其他线程占用时,该线程不会被挂起,而是在不断的消耗CPU的时间,不停的试图获取自旋锁。
互斥量是阻塞锁,当某线程无法获取互斥量时,该线程会被直接挂起,该线程不再消耗CPU时间,当其他线程释放互斥量后,操作系统会激活那个被挂起的线程,让其投入运行。
两种锁适用于不同场景:
如果是多核处理器,如果预计线程等待锁的时间很短,短到比线程两次上下文切换时间要少的情况下,使用自旋锁是划算的。
如果是多核处理器,如果预计线程等待锁的时间较长,至少比两次线程上下文切换的时间要长,建议使用互斥量。
如果是单核处理器,一般建议不要使用自旋锁。因为,在同一时间只有一个线程是处在运行状态,那如果运行线程发现无法获取锁,只能等待解锁,但因为自 身不挂起,所以那个获取到锁的线程没有办法进入运行状态,只能等到运行线程把操作系统分给它的时间片用完,才能有机会被调度。这种情况下使用自旋锁的代价 很高。
如果加锁的代码经常被调用,但竞争情况很少发生时,应该优先考虑使用自旋锁,自旋锁的开销比较小,互斥量的开销较大。
参考资料:
1. 《UNIX环境高级编程》
2. 《现代操作系统》
3. http://blog.csdn.net/swordmanwk/article/details/6819457
【UNIX环境高级编程】线程同步的更多相关文章
- UNIX环境高级编程——线程同步之互斥锁、读写锁和条件变量(小结)
一.使用互斥锁 1.初始化互斥量 pthread_mutex_t mutex =PTHREAD_MUTEX_INITIALIZER;//静态初始化互斥量 int pthread_mutex_init( ...
- UNIX环境高级编程——线程同步之条件变量以及属性
条件变量变量也是出自POSIX线程标准,另一种线程同步机制.主要用来等待某个条件的发生.可以用来同步同一进程中的各个线程.当然如果一个条件变量存放在多个进程共享的某个内存区中,那么还可以通过条件变量来 ...
- UNIX环境高级编程——线程同步之读写锁以及属性
读写锁和互斥量(互斥锁)很类似,是另一种线程同步机制,但不属于POSIX标准,可以用来同步同一进程中的各个线程.当然如果一个读写锁存放在多个进程共享的某个内存区中,那么还可以用来进行进程间的同步, 互 ...
- UNIX环境高级编程——线程同步之互斥量
互斥量(也称为互斥锁)出自POSIX线程标准,可以用来同步同一进程中的各个线程.当然如果一个互斥量存放在多个进程共享的某个内存区中,那么还可以通过互斥量来进行进程间的同步. 互斥量,从字面上就可以知道 ...
- UNIX环境高级编程——线程属性
pthread_attr_t 的缺省属性值 属性 值 结果 scope PTHREAD_SCOPE_PROCESS 新线程与进程中的其他线程发生竞争. detachstate PTHREAD_CREA ...
- Unix 环境高级编程---线程创建、同步、
一下代码主要实现了linux下线程创建的基本方法,这些都是使用默认属性的.以后有机会再探讨自定义属性的情况.主要是为了练习三种基本的线程同步方法:互斥.读写锁以及条件变量. #include < ...
- UNIX环境高级编程——线程和fork
当线程调用fork时,就为子进程创建了整个进程地址空间的副本.子进程通过继承整个地址空间的副本,也从父进程那里继承了所有互斥量.读写锁和条件变量的状态.如果父进程包含多个线程,子进程在fork返回以后 ...
- UNIX环境高级编程——线程
线程包含了表示进程内执行环境必需的信息,其中包括进程中标示线程的线程ID.一组寄存器值.栈.调度优先级和策略.信号屏蔽字.errno变量以及线程私有数据. 进程的所有信息对该进程的所有线程都是共享的, ...
- UNIX环境高级编程——线程和信号
每个线程都有自己的信号屏蔽字,但是信号的处理是进程中所有线程共享的.这意味着尽管单个线程可以阻止某些信号,但当线程修改了与某个信号相关的处理行为以后,所有的线程都必须共享这个处理行为的改变.这样如果一 ...
- UNIX环境高级编程——线程私有数据
线程私有数据(Thread-specific data,TSD):存储和查询与某个线程相关数据的一种机制. 在进程内的所有线程都共享相同的地址空间,即意味着任何声明为静态或外部变量,或在进程堆声明的变 ...
随机推荐
- 20155302 2016-2017-2 《Java程序设计》 第1周学习总结
20155302 2016-2017-2 <Java程序设计> 第1周学习总结 教材学习内容总结 浏览全书提出问题 chapter1:怎么保证现在系统在用最高版本的JRE呢?在哪里查看及升 ...
- 2017-2018-1 20155307 《信息安全系统设计基础》第san周学习总结
2017-2018-1 20155307 <信息安全系统设计基础>第三周学习总结 教材学习内容总结 无符号数和有符号数的特性,数字如何表示,IEEE标准,浮点数格式.(我感觉我把课下测试做 ...
- 20155328 2016-2017-2 《Java程序设计》第三周学习总结
20155328 2016-2017-2 <Java程序设计>第三周学习总结 教材学习内容总结 类是对象的设计图,对象是类的实例.用class定义类,用new新建一个对象. 一个原始码中可 ...
- 20155337 2016-2017-2《Java程序设计》课程总结
20155337 2016-2017-2<Java程序设计>课程总结 (按顺序)每周作业链接汇总 <我的第一篇随笔> <做中学> <Java程序设计>第 ...
- thinkphp 去除空格
- 【BZOJ3142】[HNOI2013]数列
[BZOJ3142][HNOI2013]数列 题面 洛谷 bzoj 题解 设第\(i\)天的股价为\(a_i\),记差分数组\(c_i=a_{i+1}-a_i\) 则 \[ Ans=\sum_{c_1 ...
- 洛谷3197&bzoj1008 越狱
洛谷3197&bzoj1008 越狱 Luogu bzoj 题解 所有状态减合法状态.SBT 答案为\(m^n-m*(m-1)^{n-1}\)太SB不解释 注意取膜的问题.相减可能减出负数,而 ...
- 强化学习读书笔记 - 09 - on-policy预测的近似方法
强化学习读书笔记 - 09 - on-policy预测的近似方法 参照 Reinforcement Learning: An Introduction, Richard S. Sutton and A ...
- 05-Docker架构详解
Docker 的核心组件包括: Docker 客户端 - Client Docker 服务器 - Docker daemon Docker 镜像 - Image Registry Docker 容器 ...
- MySQL5.6.14从安装到启动全过程
1.下载 地址:http://dev.mysql.com/downloads/mysql/ 这里选择的是Linux-Generic平台,下载了MySQL-5.6.14-1.linux_glibc2.5 ...