当控件的多个线程共享统一内存时,我们需要确定各个线程访问到的数据的一致性。在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> : 线程同步的更多相关文章

  1. APUE 学习笔记(八) 线程同步

    1. 进程的所有信息对该进程内的所有线程都是共享的 包括 可执行的程序文本.程序全局内存.堆内存以及文件描述符 线程包含了表示进程内执行环境必需的信息,包括线程ID.寄存器值.栈.调度优先级和策略.信 ...

  2. APUE学习笔记6——线程和线程同步

    1 概念 线程是程序执行流的最小单元.线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的 ...

  3. 第22章 java线程(2)-线程同步

    java线程(2)-线程同步 本节主要是在前面吃苹果的基础上发现问题,然后提出三种解决方式 1.线程不安全问题 什么叫线程不安全呢 即当多线程并发访问同一个资源对象的时候,可能出现不安全的问题 对于前 ...

  4. Java线程:线程的同步-同步方法

    Java线程:线程的同步-同步方法   线程的同步是保证多线程安全访问竞争资源的一种手段. 线程的同步是Java多线程编程的难点,往往开发者搞不清楚什么是竞争资源.什么时候需要考虑同步,怎么同步等等问 ...

  5. C#中的线程(二) 线程同步基础

    1.同步要领 下面的表格列展了.NET对协调或同步线程动作的可用的工具:                       简易阻止方法 构成 目的 Sleep 阻止给定的时间周期 Join 等待另一个线程 ...

  6. 基础学习day11--多线程一线程的创建,运行,同步和锁

    一.线程基本概述 1.1.进程和线程 进程:一个应用程序一般都是一个进程,正在进行的程序 每一个进程最少都有一个线程,都有一个执行顺序,该顺序是一个执行路径或者一个控制单元 线程:进程中一个独立的控制 ...

  7. C#中的线程(中)-线程同步

    1.同步要领 下面的表格列展了.NET对协调或同步线程动作的可用的工具:                       简易阻止方法 构成 目的 Sleep 阻止给定的时间周期 Join 等待另一个线程 ...

  8. Linux互斥和同步应用程序(一):posix线程和线程之间的相互排斥

    [版权声明:尊重原创,转载请保留出处:blog.csdn.net/shallnet 或 .../gentleliu,文章仅供学习交流.请勿用于商业用途] 有了进程的概念,为何还要使用线程呢? 首先,回 ...

  9. (转)Java线程:线程的同步与锁

      Java线程:线程的同步与锁       一.同步问题提出   线程的同步是为了防止多个线程访问一个数据对象时,对数据造成的破坏. 例如:两个线程ThreadA.ThreadB都操作同一个对象Fo ...

  10. -1-5 java 多线程 概念 进程 线程区别联系 java创建线程方式 线程组 线程池概念 线程安全 同步 同步代码块 Lock锁 sleep()和wait()方法的区别 为什么wait(),notify(),notifyAll()等方法都定义在Object类中

     本文关键词: java 多线程 概念 进程 线程区别联系 java创建线程方式 线程组 线程池概念 线程安全 同步 同步代码块 Lock锁  sleep()和wait()方法的区别 为什么wait( ...

随机推荐

  1. 201521123092《java程序设计》第14周学习总结

    1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结多数据库相关内容. 2. 书面作业 1. MySQL数据库基本操作 建立数据库,将自己的姓名.学号作为一条记录插入.(截图,需出现自 ...

  2. Git 查看/修改用户名、邮箱

    用户名和邮箱地址的作用 用户名和邮箱地址是本地Git客户端的一个变量,不随git库而改变. 每次commit都会用用户名和邮箱纪录. github的contributions统计就是按邮箱来统计的. ...

  3. javascript:12种JavaScript MVC框架之比较

    Gordon L. Hempton是西雅图的一位黑客和设计师,他花费了几个月的时间研究和比较了12种流行的JavaScript MVC框架,并在博客中总结了每种框架的优缺点,最终的结果是,Ember. ...

  4. .NET 动态脚本语言

    Script.NET (S#) 是一种允许为你的应用程序自定义行为,与本地.NET对象.类型和组件交互动态的脚本语言.托管应用程序本身可以改变一个xml配置脚本运行时的默认行为,更换绑定的方法,属性, ...

  5. C语言bitmap的使用技巧

    bitmap是一种以位的状态来表示某种特性的状态的一种操作方式,类似嵌入式中的寄存器操作,在表示某种特性enable/disable的时候很适用且占用的内存空间也很小 比如:做过交换机或者企业网管,路 ...

  6. 看源码和写demo是一种比较容易提升的方式

    github就是要这么用才行.看别人的源码,就能了解到很多规范,而写demo,就是自己写出自己的代码.莫欺少年穷

  7. javascript插入before(),after()新DOM方法

    随着web的技术突飞猛进的发展.HTML5 ES6等新技术的发展,与此同时DOM等标准也在悄悄的进步,各大浏览器也在悄悄的发展适配新的属性和方法,今天我们来看看Javascript新的DOM的方法 二 ...

  8. Python迭代器,生成器--精华中的精华

    1. 迭代器 迭代器是访问集合元素的一种方式.迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束.迭代器只能往前不会后退,不过这也没什么,因为人们很少在迭代途中往后退.另外,迭代器的一大 ...

  9. Python 编程基础之高阶函数篇(一)

      高阶函数:能接受函数作为参数的函数. 如: f=abs def   add(x,y,f): return f(x)+f(y) 如果我们用:add(-5,9,f)来调用该高阶函数,则返回结果为:14 ...

  10. CentOS 7搭建LAMP环境(一)

    CentOS是Linux发行版之一,它是来自于Red Hat Enterprise Linux依照开放源代码规定释出的源代码所编译而成.由于出自同样的源代码,因此有些要求高度稳定性的服务器以CentO ...