线程同步

    同属于一个进程的不同线程是共享内存的,因而在执行过程中需要考虑数据的一致性。
    假设:进程有一变量i=0,线程A执行i++,线程B执行i++,那么最终i的取值是多少呢?似乎一定是i=2;其实不然,如果没有考虑线程同步,i的取值可能是1.我们先考虑自加操作的过程:a,首先将内存中i的值copy到寄存器;b,对寄存器中i的copy进行自加;c,将寄存器中自加的结果返回到内存中。回到例子,如果线程A执行完abc三个步骤,线程B在执行者三个步骤,那么结果就应该为2.但是自加不是原子操作,假如执行过程是a(A),
a(B), b(A),b(B),c(A),c(B)。那么执行的过程就是AB都把i原值i=0copy到寄存器,进行自加,得到结果1,然后分别有把值1赋值到内存中的i,导致i结果为1.
    之所以可能出现上面非期望的结果(i=1而不是i=2),是因为i++不是原子操作。为了解决这个问题,引入互斥锁。

互斥锁 Mutexes

    互斥锁通过pthreads的互斥接口来保证数据在某一时间里只能被一个线程访问。
    互斥锁的使用过程大致是:1)访问共享资源前加锁,2)访问共享资源,3)访问加速后解锁。(在这个过程中互斥量扮演一个锁的角色)
    互斥量使用结构体pthread_mutex_t表示,使用的接口如下:
互斥量的初始化与销毁:
#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);
Both return: 0 if OK, error number on failure
    动态创建的互斥量mutex才需要使用pthread_mutex_destroy,静态创建互斥量时,可以直接mutex=PTHREAD_MUTEX_INITIALIZER
进行初始化,而不调用pthread_mutex_init函数。
    参数attr表示互斥量的属性。

加锁与解锁:
    
#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_timedlock(pthread_mutex_t *restrict mutex,const struct timespec *restrict tsptr);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
All return: 0 if OK, error number on failure
 使用pthread_mutex_lock 时,如果互斥量已经被其他线程加锁,那么会阻塞,知道该互斥量被解锁再对互斥量加锁。
 使用pthread_mutex_trylock时,如果互斥量已经被其他线程加锁,则函数返回错误。如果未被加锁,那么就对互斥量加锁。
 使用pthread_mutex_timedlock 时,尝试加锁,阻塞(最多阻塞到时间点tsptr),如果在tsptr还无法加锁,则返回错误。
关于tsptr,它是一个时间点,比如我们要等待3min,则结果tsptr应该是当前时间加上3min。

死锁与避免死锁

产生死锁的几种情况:
1)线程对同一互斥量加锁两次,类似下面的情况:
pthread_mutex_lock(mutex);
pthread_mutex_lock(mutex);
pthread_mutex_unlock(mutex);
pthread_mutex_unlock(mutex);
在第二行发生阻塞,死锁。
2)多个互斥量,且不同线程各自锁住一个互斥量,并都在请求另一个互斥量时阻塞:
//thread A
pthread_mutex_lock(mutex1);
pthread_mutex_lock(mutex2);
pthread_mutex_unlock(mutex2);
pthread_mutex_unlock(mutex1);
//thread B
pthread_mutex_lock(mutex2);
pthread_mutex_lock(mutex1);
pthread_mutex_unlock(mutex1);
pthread_mutex_unlock(mutex2);
A锁住mutex1,在请求mutex2时,B已经锁住了mutex2.   而B在请求mutex1时,又被A锁住了。

避免死锁
    对于上面两种死锁情况,第一种是比较容易避免的,因为大多数情况下不会对同一个互斥量加锁两次,产生该类错误一般是程序员问题。
    针对第二种情况, 我们通过要求同一进程的各个进程都对所有的互斥量以相同的顺序进行加锁,来避免产生死锁。也就是对于任何一个线程pthread_mutex_lock(mutex1);一定
在pthread_mutex_lock(mutex2); 之前。
    一般我们使用一个散列列表锁来帮助我们实现,类似:
struct foo {
    int f_count;
    pthread_mutex_t f_lock;
    int f_id;
    /* ... more stuff here ... */
};

   

读写锁

    读写锁的作用是针对不同方式的共享资源访问,提供了更高的并行性。
    关键点:
    1)读写锁在读加锁状态时,任何其他的读加锁都可以获得访问权。
    2)写加锁只有在读写锁没有被加锁(不管在读加锁还是写加锁)的情况下才能成功,否则阻塞
    3)读写锁在写加锁状态时,任何其他的针对该读写锁的加锁都被阻塞。
    在实现过程中,为避免一直发生读加锁,导致写加锁一直阻塞,通常当发生写加锁请求时,会先阻塞随后的读加锁。以此避免等待着的写加锁一直得不到满足。
    下面列出读写锁的一些接口(类似互斥锁):
初始化与销毁
#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);
Both return: 0 if OK, error number on failure
加锁与解锁:
#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);
All return: 0 if OK, error number on failure
#include <pthread.h>
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
Both return: 0 if OK, error number on failure
#include <pthread.h>
#include <time.h>
int pthread_rwlock_timedrdlock(pthread_rwlock_t *restrictrwlock,const struct timespec*restrict tsptr);
int pthread_rwlock_timedwrlock(pthread_rwlock_t *restrictrwlock,const struct timespec *restric ttsptr);

读写锁的接口用法基本与互斥锁一样,知识其逻辑功能分读和写两种情况而已。

条件变量

    条件变量允许线程以无竞争的方式等待某个条件的发生。
    条件变量用结构体:pthread_cond_t表示。
    条件变量的接口函数:

#include <pthread.h>
int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr);
int pthread_cond_destroy(pthread_cond_t *cond);
Both return: 0 if OK, error number on failure
这是初始化和去初始化的过程。同样可以使用PTHREAD_COND_INITIALIZER对静态创建的条件变量进行初始化。
#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 tsptr);
pthread_cond_wait是理解条件变量的关键。该函数会发生下面过程
1)解锁互斥量mutex
2)阻塞等待条件变量唤醒,关于唤醒函数后面会介绍
3)加锁互斥量mutex。

关于pthread_cond_timedwait注意tsptr是表示一个时间点,比如等待3min中,则tsptr的值应该是当前时间加上3min

唤醒函数:
#include <pthread.h>
int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);
Both return: 0 if OK, error number on failure
Example:
#include <pthread.h>
struct msg {
struct msg *m_next;
    /* ... more stuff here ... */
};
struct msg *workq;
pthread_cond_t qready = PTHREAD_COND_INITIALIZER;
pthread_mutex_t qlock = PTHREAD_MUTEX_INITIALIZER;

void
process_msg(void)
{
    struct msg *mp;
    for (;;) {
        pthread_mutex_lock(&qlock);
        while (workq == NULL)
            pthread_cond_wait(&qready, &qlock);
        mp = workq;
        workq = mp->m_next;
        pthread_mutex_unlock(&qlock);
        /* now process the message mp */
    }
}
void
enqueue_msg(struct msg *mp)
{
    pthread_mutex_lock(&qlock);
    mp->m_next = workq;
    workq = mp;
    pthread_mutex_unlock(&qlock);
    pthread_cond_signal(&qready);
}

Spin lock 自旋锁

    自旋锁与互斥锁类似。只是起阻塞的方式不同。对于互斥锁而言,如果互斥锁被其他线程锁住了,那么本线程要加锁时通过睡眠方式等待。而对于自旋锁,它加锁时,如果自旋锁被其它线程加锁,则本线程以循环方式等待,也就是在那里一直循环。 
    自旋锁的作用是提高加锁的效率。它一般用于使用者保持锁时间较短的情况。也就是说加锁和解锁时间很短的情况。
    关于自旋锁的接口:
#include <pthread.h>
int pthread_spin_init(pthread_spinlock_t *lock,intpshared);
int pthread_spin_destroy(pthread_spinlock_t *lock);
Both return: 0 if OK, error number on failure
    
#include <pthread.h>
int pthread_spin_lock(pthread_spinlock_t *lock);
int pthread_spin_trylock(pthread_spinlock_t *lock);
int pthread_spin_unlock(pthread_spinlock_t *lock);
All return: 0 if OK, error number on failure

Barriers 计数锁

    计数锁用pthread_barrier_ 表示,它充当栏杆,阻塞线程,直到指定数目的线程到齐。
    接口:
#include <pthread.h>
int pthread_barrier_init(pthread_barrier_t *restrictbarrier,const pthread_barrierattr_t *restrict attr,unsigned int count);
int pthread_barrier_destroy(pthread_barrier_t *barrier);
Both return: 0 if OK, error number on failure
参数count用于指定要等待的线程数目。
#include <pthread.h>
int pthread_barrier_wait(pthread_barrier_t *barrier);
Returns: 0 orPTHREAD_BARRIER_SERIAL_THREADif OK, error number on failure
pthread_barrier_wait 用来是线程进入等待,然后检测计数锁是否满足释放条件了。
具体通俗的例子,8个人参加赛跑(count=0),每个运动员到起跑线 执行pthread_barrier_wait 等待其它运动员,并通知裁判(相当于内核)我到了,裁判在8个运动员都到了时,开始比赛 pthread_barrier_wait 返回。






















APUE学习笔记——11 线程同步、互斥锁、自旋锁、条件变量的更多相关文章

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

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

  2. APUE学习笔记——11 线程基础

    线程标识 线程由线程号进行标识.线程号仅在线程所属的进程环境中有效.也就是说属于不同进程的两个线程可能线程号一样. 线程标识用结构体pthread_t tid表示.与线程Id相关的函数如下: 比较两个 ...

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

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

  4. python学习笔记11 ----线程、进程、协程

    进程.线程.协程的概念 进程和线程是操作系统中两个很重要的概念,对于一般的程序,可能有若干个进程,每一个进程有若干个同时执行的线程.进程是资源管理的最小单位,线程是程序执行的最小单位(线程可共享同一进 ...

  5. linux学习笔记之线程同步机制

    一.基础知识. 1:线程同步机制:互斥量,读写锁,条件变量,自旋锁,屏障. 1,互斥量:每个进程访问被互斥量保护的资源时,都需要先对互斥量进行判断. 1)互斥量重要属性:进程共享属性,健壮属性,类型属 ...

  6. Linux学习笔记21——线程同步的两种方式

    一  用信号量同步 1 信号量函数的名字都以sem_开头,线程中使用的基本信号量函数有4个 2 创建信号量 #include<semaphore.h> int sem_init(sem_t ...

  7. 第8章 用户模式下的线程同步(4)_条件变量(Condition Variable)

    8.6 条件变量(Condition Variables)——可利用临界区或SRWLock锁来实现 8.6.1 条件变量的使用 (1)条件变量机制就是为了简化 “生产者-消费者”问题而设计的一种线程同 ...

  8. C#学习笔记之线程 - 同步上下文

    同步上下文(Synchronization Contexts) 手动使用锁的一个替代方案是去声明锁.通过派生ContextBoundObject和应用Synchronization属性,你告诉CLR自 ...

  9. C++11并发学习之三:线程同步(转载)

    C++11并发学习之三:线程同步 1.<mutex> 头文件介绍 Mutex又称互斥量,C++ 11中与 Mutex 相关的类(包括锁类型)和函数都声明在 <mutex> 头文 ...

随机推荐

  1. KVM网络性能调优

    首先,我给大家看一张图,这张图是数据包从虚拟机开始然后最后到物理网卡的过程. 我们分析下这张图,虚拟机有数据包肯定是先走虚拟机自身的那张虚拟网卡,然后发到中间的虚拟化层,再然后是传到宿主机里的内核网桥 ...

  2. 20145303 《Java程序设计》第六周学习总结

    20145303 <Java程序设计>第六周学习总结 教材学习内容总结 第十章:输入/输出 (InputStream与OutputStream) 1.Java将输入/输出抽象化为串流,数据 ...

  3. 20145307JAVA学习期末总结

    20145307<Java程序设计>课程总结 每周读书笔记链接汇总 20145307 <Java程序设计>第一周学习总结:http://www.cnblogs.com/Jcle ...

  4. Jquery4 过滤选择器

    学习要点: 1.基本过滤器 2.内容过滤器 3.可见性过滤器 4.子元素过滤器 5.其他方法 过滤选择器简称:过滤器.它其实也是一种选择器,而这种选择器类似 CSS3里的伪类,可以让不支持 CSS3 ...

  5. 学Git,用Git ③

    不知道我前面是否将git讲清楚了,这里再稍微总结一下git的一个重要功能用法,同时增加两个很实用的git使用技巧. 1.git"读档"与git"回退" 我发现我 ...

  6. 安装完kali需要做的一些事情

    1. 没有声音的问题[ kali ] 参考:http://tieba.baidu.com/p/4343219808 用pulseaudio --start会看到一些信息,提示类似root用户之类的 我 ...

  7. MongoDB-与Python交互

    与python交互 点击查看官方文档 安装python包 进入虚拟环境 sudo pip install pymongo 或源码安装 python setup.py 引入包pymongo import ...

  8. struts2中各个jar包作用 (转)

    Struts2.3.4 所需的Jar包及介绍 Jar包的分类 jar包名称 jar包版本 jar包 文件名 jar包 的作用 jar包内包含的主要包路径及主要类 依赖的自有jar包名称 依赖的第三方j ...

  9. Elasticsearch之IKAnalyzer的过滤停止词

    它在哪里呢? 非常重要! [hadoop@HadoopMaster custom]$ pwd/home/hadoop/app/elasticsearch-2.4.3/plugins/ik/config ...

  10. 1568: [JSOI2008]Blue Mary开公司

    1568: [JSOI2008]Blue Mary开公司 题目描述 传送门 题目分析 简单分析可以发现就是不停给出了\(n\)条直线,要求每次给出一条直线后求出所有直线在横坐标为\(x\)时\(y\) ...