APUE 4 - 线程<2> : 线程同步
当控件的多个线程共享统一内存时,我们需要确定各个线程访问到的数据的一致性。在cpu结构中,修改操作由多个内存读写周期(memory cycle),而在这些内存周期之间, 有可能会发生其他线程的内存读操作,这样就会产生多线程之间的数据一致性问题。
互斥锁 mutex
我们可以通过线程互斥锁接口(pthreads mutual-exclusion interfaces)来保证同一时间只有一个线程访问我们的数据。一个mutex变量使用pthread_mutex_t数据类型来表示。在我们使用这个变量前,我们必须使用常量PTHREAD_MUTEX_INITIALIZER或调用pthread_mutex_init对其进行初始化。如果我们动态的分配mutex(通过调用pthread_mutex_init),我们需要在释放内存前调用pthread_mutex_destroy。
#include <pthread.h> /* Both return 0 if OK, error number on failure */
int pthread_mutex_init(pthread_mutex_t* restrict mutex, const pthread_mutexattr_t* restrict attr); int pthread_mutex_destroy(pthread_mutex_t* mutex);
可以通过 pthread_mutex_lock对mutex加锁,如果mutex已经被锁住,那么其他对mutex进行加锁的线程会被阻塞,直到mutex被解锁。可以通过pthread_mutex_unlock来解锁mutex。
#include <pthread.h> /* return 0 if OK, error number on failure */
int pthread_mutex_lock(pthread_mutex_t* mutex); /*
如果mutex处于unlocked状态,此方法会对mutex加锁并返回0,
否则返回EBUSY,不会对mutex加锁
*/
int pthread_mutex_trylock(pthread_mutex_t* mutex); /* return 0 if OK, error number on failure */
int pthread_mutex_unlock(pthread_mutex_t* mutex);
避免死锁
一个线程如果试图对同一个mutex进行连续两次加锁,那么它将处于死锁状态。而确实有那么几种明显的使用mutex会产生死锁。举例来说,当我们程序中有多个mutex的时候,如果我们让一个线程锁住第一个mutex,然后对第二个mutex进行加锁,而与此同时如果另一个线程锁住第二个mutex并试图对第一个mutex进行加锁,那么此时程序就会进入死锁状态。
我们可以通过消息的控制对mutex的加锁状态来避免死锁。如果我们所有的线程对所有的mutex都保持同一种加锁顺序,那么程序中永远不会出现加锁现象。然后有些时候,由于程序的架构问题我们无法保证加锁顺序,此时我们就必须采取一些其他的措施。在这种情况下我们应该先释放掉以加锁的mutex,然后重新尝试加锁。这种情况下我们可以使用pthread_mutex_trylock,如果pthread_mutex_trylock成功我们可以继续进行其他操作,否则我们应该释放掉以加锁的mutex,清理程序,然后在重试加锁。
可以使用pthread_mutex_timedlock方法绑定加锁阻塞时间,如果规定时间内没有成功对mutex加锁,它返回ETIMEOUT。
#include <pthread.h>
#include <time.h> /* tspr is absolute time, Return 0 if OK, error number on failure. */
int pthread_mutex_timedlock(pthread_mutex_t* restrict mutex, const struct timespec* restrict tsptr);
读写锁
读写锁也称为共享锁,它与mutex相似,但他们提供了对并行机制更高精度的控制。对于mutex来讲,他只有两种状态:locked和unlocked,并且,同一时间内只有一个线程可以锁住它。而对于读写锁来说,他们有三种状态:读锁状态(locked in read mode),写锁状态(locked in write mode)和解锁状态(unlocked)。同一时间只有一个线程可以让读写锁处于写锁状态,而同一时间可以有多个线程让读写锁处于读锁状态。
当一个读写锁处于写锁状态时,所有尝试对其加锁的线程都会处于阻塞状态,知道读写锁的写锁状态被解除。当读写锁处于读锁状态时,所有对其进行写锁操作的线程都会被阻塞,但是对其进行读锁操作的线程不会被阻塞。通常,当有对处于读写锁进行写锁操作的线程被阻塞时,其他队此读写锁进行读操作的线程也会被阻塞。
读写锁在使用前必须初始化,同样的,在释放内存钱必须销毁。也可以使用常量PTHREAD_RWLOCK_INITIALIZER初始化rwlock。
#include <pthread.h> /* All return 0 if OK, error number on failure */
int pthread_rwlock_init(pthread_rwlock_t* restrict rwlock, const pthread_rwlockattr_t* restrict attr);
int pthread_rwlock_destroy(pthread_rwlock_t* rwlock); int pthread_rwlock_rdlock(pthread_rwlock_t* rwlock);
int pthread_rwlock_wrlock(pthread_rwlock_t* rwlock);
/* unlock any type of rwlock */
int pthread_rwlock_unlock(pthread_rwlock_t* rwlock); int pthread_rwlock_tryrdlock(pthread_rwlock_t* rwlock);
int pthread_rwlock_truwrlock(pthread_rwlock_t* rwlock);
rwlock with timeouts
#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);
条件变量(Condition Variables)
条件变量是另一种线程同步机制,他们提供了线程交互点。当与mutex一起使用时, 条件变脸可以是线下以自由竞争的方式来等待任意条件的发生。这个条件本身是被一个mutex保护的。线程在改变条件状态之前必须对这个mutex加锁,其他线程在获取到这个mutex权限前不会知道这个变化,因为mutex必须处于加锁状态才能读取到条件的值。
条件变量在使用前必须进行初始化,我们可以通过常量PTHREAD_COND_INITAILIZER或者调用pthread_cond_init来初始化条件变量, 若条件变量是动态分配的,那么我么需要使用pthread_cond_destroy来释放条件变量。
#include <pthread.h> /* Both return 0 if OK, error number on failure */
int pthread_cond_init(pthread_cond_t* restrict cond, const pthread_condattr_t* restrict attr); int pthread_cond_destroy(pthread_cond_t* cond);
wait for condition to be true
#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, constr struct timespec* restict tsptr);
mutex用于保护条件,以防止多个线程调用pthread_cond_wait的竞态条件。调用线程在调用前需要对mutex加锁,pthread_cond_wait会自动将调用线程放到此条件的等待线程列表中然后等待条件的发生并解锁mutex。当phtread_cond_wait返回时(正常情况是条件被满足时),mutex被重新置位加锁状态。在pthread_cond_wait或phread_cond_timedwait方法返回时,线程需要重新检测条件,因为其他线程在此时可能会更改了条件, 因此通常我们将等待放入循环中。
有两个函数可以通知条件已满足,pthread_cond_signal可以唤醒至少一个条件等待线程,而pthread_cond_broadcast会唤醒所有的条件等待线程。
#include <pthread.h> int pthread_cond_signal(pthread_cond_t* cond);
int pthread_cond_broadcast(pthread_cond_t* cond);
自旋锁(spin locks)
自旋锁与互斥锁相似,只不过它不是以休眠的方式阻塞进程,而是通过busy-waiting(spinning)知道获取到锁权限。自旋锁通常用于锁住状态时间较短和线程不愿遭受调度成本的情况。自旋锁通常用于实现其他类型锁的底层原型。依赖于系统结构,他们可以通过test-and-set指令高效的实现。尽管高效,他们会导致浪费CPU资源。
#include <pthread.h> int pthread_spin_init(pthread_spinlock_t* lock, int pshared);
int pthread_spin_destroy(pthread_spinlock_t* lock); int pthread_spin_lock(pthread_spinlock_t* lock);
int pthread_spin_trylock(pthread_spinlock_t* lock);
int pthread_spin_unlock(pthread_spinlock_t* lock);
Barriers
Barriers 是一种同步机制,它可以用于并行环境下协调多线程工作。barriers允许每个线程都等待直到所有协调线程都达到某个点,然后从它阻塞的地方继续执行。pthread_jion就是一种形式的barrier——它使一个线程等待直到另一个线程退出。Barrier对象要比他更通用一些,他们允许任意数量的线程等待直到所有的线程完成执行,但线程不必退出。
#include <pthread.h> int pthread_barrier_init(pthread_barrier_t* restrict barrier, const pthread_barrierattr_t* restrict attr, unsigned int count);
int pthread_barrier_destroy(pthread_barrier_t* barrier);
count参数用于指定在所有线程允许继续执行前必须达到barrier的线程数量。
我们使用pthread_barrier_wait函数来指示此线程以完成他的工作并准备好等待其他线程赶上进度。
#include <pthread.h> /* Rturns 0 or PTHREAD_BARRIER_SERIAL_THREAD if OK, error number on failure */
int pthread_barrier_wait(pthread_barrier_t* barrier);
调用pthread_barrier_wait的线程会被置于休眠状态如果如果barrier数量未达到init时设置的数量。如果调用线程是最后一个线程,即barrier数量达到了初始化是设置的数量,此时所有的线程都会被唤醒。
总结
线程基本同步机制有 mutex、rwlock、condlock、spin lock、barrier。mutex同一时间内只有一个线程处于locked状态;rwlock提供了对读写更精细的控制;condlock通过条件变量为多线程同步提供了线程交互点;spin lock是高效的mutex,但是浪费cpu性能,一般用户层编程用不到此类型的锁;barrier提供了一种良好的多线程协调机制;实际项目中我们应该根据几种锁的特性来合理使用,并注意要避免死锁问题。
APUE 4 - 线程<2> : 线程同步的更多相关文章
- APUE 学习笔记(八) 线程同步
1. 进程的所有信息对该进程内的所有线程都是共享的 包括 可执行的程序文本.程序全局内存.堆内存以及文件描述符 线程包含了表示进程内执行环境必需的信息,包括线程ID.寄存器值.栈.调度优先级和策略.信 ...
- APUE学习笔记6——线程和线程同步
1 概念 线程是程序执行流的最小单元.线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的 ...
- 第22章 java线程(2)-线程同步
java线程(2)-线程同步 本节主要是在前面吃苹果的基础上发现问题,然后提出三种解决方式 1.线程不安全问题 什么叫线程不安全呢 即当多线程并发访问同一个资源对象的时候,可能出现不安全的问题 对于前 ...
- Java线程:线程的同步-同步方法
Java线程:线程的同步-同步方法 线程的同步是保证多线程安全访问竞争资源的一种手段. 线程的同步是Java多线程编程的难点,往往开发者搞不清楚什么是竞争资源.什么时候需要考虑同步,怎么同步等等问 ...
- C#中的线程(二) 线程同步基础
1.同步要领 下面的表格列展了.NET对协调或同步线程动作的可用的工具: 简易阻止方法 构成 目的 Sleep 阻止给定的时间周期 Join 等待另一个线程 ...
- 基础学习day11--多线程一线程的创建,运行,同步和锁
一.线程基本概述 1.1.进程和线程 进程:一个应用程序一般都是一个进程,正在进行的程序 每一个进程最少都有一个线程,都有一个执行顺序,该顺序是一个执行路径或者一个控制单元 线程:进程中一个独立的控制 ...
- C#中的线程(中)-线程同步
1.同步要领 下面的表格列展了.NET对协调或同步线程动作的可用的工具: 简易阻止方法 构成 目的 Sleep 阻止给定的时间周期 Join 等待另一个线程 ...
- Linux互斥和同步应用程序(一):posix线程和线程之间的相互排斥
[版权声明:尊重原创,转载请保留出处:blog.csdn.net/shallnet 或 .../gentleliu,文章仅供学习交流.请勿用于商业用途] 有了进程的概念,为何还要使用线程呢? 首先,回 ...
- (转)Java线程:线程的同步与锁
Java线程:线程的同步与锁 一.同步问题提出 线程的同步是为了防止多个线程访问一个数据对象时,对数据造成的破坏. 例如:两个线程ThreadA.ThreadB都操作同一个对象Fo ...
- -1-5 java 多线程 概念 进程 线程区别联系 java创建线程方式 线程组 线程池概念 线程安全 同步 同步代码块 Lock锁 sleep()和wait()方法的区别 为什么wait(),notify(),notifyAll()等方法都定义在Object类中
本文关键词: java 多线程 概念 进程 线程区别联系 java创建线程方式 线程组 线程池概念 线程安全 同步 同步代码块 Lock锁 sleep()和wait()方法的区别 为什么wait( ...
随机推荐
- 201521123119《Java程序设计》第10周学习总结
1. 本周学习总结 Q1.1 以你喜欢的方式(思维导图或其他)归纳总结异常与多线程相关内容. 2. 书面作业 本次PTA作业题集异常.多线程 Q1.finally 题目4-2 Q1.1 截图你的提交结 ...
- 安装wampserve之前需要安装vc++2012.
本人是64位系统下载了wampserver3.0.6之后安装好,启动报错缺少msvcr110.dll. 于是从网上下载了msvcr110.dll放到了windows的syswow64文件夹下,甚至还重 ...
- SpringMVC第五篇【方法返回值、数据回显、idea下配置虚拟目录、文件上传】
Controller方法返回值 Controller方法的返回值其实就几种类型,我们来总结一下-. void String ModelAndView redirect重定向 forward转发 数据回 ...
- Spring定时器的使用详解
写个最简单的demo吧,反正睡前没什么事儿,来祸害一下园子~~虽然我菜,但是我不会承认啊,哈哈哈 明天详细补充点儿吧,很晚了,不睡觉的程序员不是好程序员,我总能给自己找借口~~~ //spring开启 ...
- 彻底弄懂AngularJS中的transclusion
点击查看AngularJS系列目录 彻底弄懂AngularJS中的transclusion AngularJS中指令的重要性是不言而喻的,指令让我们可以创建自己的HTML标记,它将自定义元素变成了一个 ...
- 强大的桌面用 PDF 重排工具:K2pdfopt 简明教程
用 Kindle 阅读 PDF 一直以来都遭到小伙伴们的无限吐槽,在那 Kindle 还能越狱的时代,我们有 Koreader 之类优秀的 Kindle 第三方插件实现 PDF 文档的实时重排,但是随 ...
- SQL语言知识点总结
1.DQL.DML.DDL.DCL的概念与区别 一.SQL(Structure Query Language)语言是数据库的核心语言. SQL的发展是从1974年开始的,其发展过程如下: 1974年- ...
- 2008-2009 ACM-ICPC, NEERC, Southern Subregional ContestF
Problem F. Text Editor Input file: stdin Output file: stdout Time limit: 1 second Memory limit: 64 m ...
- 关于离线底图和离线shp文件的加载
首先底图是我自己用百度地图18级别的瓦片图在armap中制作的TPK文件,shp图层是我用同样的百度地图18级别的瓦片图矢量化的,二者在arcmap中的空间参考是一致的,所以我以为在移动端加入的时候二 ...
- 关于el-dialog,我更推荐的用法
最近的项目里用上了vue和element-ui.vue这种轻量级渐进式框架的舒适自不必说,但一直困扰着我的,是如何方便又优雅的弹出模态dialog... 对于我这种在jquery出现之前就用docum ...