linux多线程同步概览

  • 临界区
  • 互斥锁 mutex
    • 基本函数
  • pthread_mutex_destroy 何时调用?
    • 互斥锁类型?
    • 互斥量和自旋锁的区别
  • 条件变量 condition variable
    • 基本函数
    • 流程
  • 读写锁 reader-writer lock
    • 单读多写的场景
  • 自旋锁 spin lock
    • 初始化自旋锁
    • 获得一个自旋锁
    • 尝试获取一个自旋锁
    • 释放(解锁)一个自旋锁
    • 销毁一个自旋锁
  • 信号量 semphore
  • unique_lock
    • 成员函数
  • thread local
    • __thread

临界区

当两个线程竞争同一资源时,如果对资源的访问顺序敏感,就称存在竞态条件。导致竞态条件发生的代码区称作临界区。

互斥锁 mutex

基本函数

#include <phread.h>
pthread_mutex_t mutex_x = PTHREAD_MUTEX_INITIALIZER;  // 静态锁
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);  // 动态锁// 锁操作int pthread_mutex_lock(pthread_mutex_t *mutex);  // 如果lock的时候改锁已经被占用,那么会阻塞到可用为止,成功后返回0,失败后返回错误代码
int pthread_mutex_unlock(pthread_mutex_t *mutex);  // 如果unlock一个没有被lock的锁, 那么会导致没有定义的行为或者返回错误 https://blog.csdn.net/maray/article/details/4071096
int pthread_mutex_trylock(pthread_mutex_t *muytex);  // 如果锁已经被占用,那么不是阻塞,而是直接返回EBUSY.  <-- int 返回值, 不是errno
int pthread_mutex_destroy(pthread_mutex_t *mutex);
int pthread_mutex_timedlock(pthread_mutex_t *restrict mutex, const struct timespec *restrict abs_timeout);

pthread_mutex_destroy 何时调用?

  1. 如果使用静态锁PTHREAD_MUTEX_INITIALIZER,那么没有必要调用pthread_mutex_destroy.
  2. 如果使用动态锁,在destroy之前必须先调用pthread_mutex_lock() 加锁。
  3. 注意! 是destroy,而不是destory

互斥锁类型?

互斥量和自旋锁的区别

  • 从实现原理上来讲,Mutex属于sleep-waiting类型的 锁。例如在一个双核的机器上有两个线程(线程A和线程B),它们分别运行在Core0和Core1上。假设线程A想要通过 pthread_mutex_lock操作去得到一个临界区的锁,而此时这个锁正被线程B所持有,那么线程A就会被阻塞(blocking),Core0 会在此时进行上下文切换(Context Switch)将线程A置于等待队列中,此时Core0就可以运行其他的任务(例如另一个线程C)而不必进行忙等待。而Spin lock则不然,它属于busy-waiting类型的锁,如果线程A是使用pthread_spin_lock操作去请求锁,那么线程A就会一直在 Core0上进行忙等待并不停的进行锁请求,直到得到这个锁为止。
  • 如果大家去查阅Linux glibc中对pthreads API的实现NPTL(Native POSIX Thread Library) 的源码的话(使用”getconf GNU_LIBPTHREAD_VERSION”命令可以得到我们系统中NPTL的版本号),就会发现pthread_mutex_lock()操作如果 没有锁成功的话就会调用system_wait()的系统调用并将当前线程加入该mutex的等待队列里。而spin lock则可以理解为在一个while(1)循环中用内嵌的汇编代码实现的锁操作(印象中看过一篇论文介绍说在linux内核中spin lock操作只需要两条CPU指令,解锁操作只用一条指令就可以完成)。有兴趣的朋友可以参考另一个名为sanos的微内核中pthreds API的实现:mutex.c spinlock.c,尽管与NPTL中的代码实现不尽相同,但是因为它的实现非常简单易懂,对我们理解spin lock和mutex的特性还是很有帮助的。
  • 对于自旋锁来说,它只需要消耗很少的资源来建立锁;随后当线程被阻塞时,它就会一直重复检查看锁是否可用了,也就是说当自旋锁处于等待状态时它会一直消耗CPU时间。
  • 对于互斥锁来说,与自旋锁相比它需要消耗大量的系统资源来建立锁;随后当线程被阻塞时,线程的调度状态被修改,并且线程被加入等待线程队列;最后当锁可用 时,在获取锁之前,线程会被从等待队列取出并更改其调度状态;但是在线程被阻塞期间,它不消耗CPU资源。
  • 因此自旋锁和互斥锁适用于不同的场景。自旋锁适用于那些仅需要阻塞很短时间的场景,而互斥锁适用于那些可能会阻塞很长时间的场景。

条件变量 condition variable

条件变量要搭配mutex使用

基本函数

#include <pthread.h>
#include <sys/time.h>  // for timespec.
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;  // 静态创建
int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr);  // 动态创建, 条件变量没有attr, 所以第二个参数传入NULL
int pthread_cond_destroy(pthread_cond_t *cond);
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);  // wait之后程序会阻塞在这里, 等待signal的触发
int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime);  // 如果在timeout时间前条件没有满足, 则返回ETIMEOUT。
int pthread_cond_signal(pthread_cond_t *cond);  // 激活一个等待的线程,不会产生“惊群现象”,但是如果做signal的时候没有线程在阻塞等待,它也会正确返回
int pthread_cond_broadcast(pthread_cond_t *cond);  // 激活所有等待的线程

流程

  1. mutex加锁
  2. cond条件锁挂起(调用 pthread_cond_wait)之后, mutex解锁
  3. 条件锁收到signal信号释放, mutex加锁
  4. 条件变量外面经常搭配一个while循环,其逻辑是,在第一次进入条件锁的时候,逻辑为true,但是其他线程发出signal激活条件变量的时候,while循环为false
  5. 注意,这个地方只能用while,而不能用if(), 原因是spurious wakeup (《muduo》P41)
    1. 就算没有任何线程发signal激活因条件变量而睡眠的线程,这个条件变量线程仍然可能醒来。
    2. wiki: “This means that when you wait on a condition variable, the wait may (occasionally) return when no thread specifically broadcast or signaled that condition variable. Spurious wakeups may sound strange, but on some multiprocessor systems, making condition wakeup completely predictable might substantially slow all condition variable operations. The race conditions that cause spurious wakeups should be considered rare.”

读写锁 reader-writer lock

单读多写的场景

  1. 读写锁
  2. 双缓存
    1. https://www.cnblogs.com/openlib/p/5361888.html
    2. https://baike.baidu.com/item/%E5%8D%95%E5%86%99%E5%A4%9A%E8%AF%BB/22692499?fr=aladdin

自旋锁 spin lock

  • 由于自旋时不释放CPU,因而持有自旋锁的线程应该尽快释放自旋锁,否则等待该自旋锁的线程会一直在那里自旋,这就会浪费CPU时间。
  • 持有自旋锁的线程在sleep之前应该释放自旋锁以便其它线程可以获得自旋锁。(在内核编程中,如果持有自旋锁的代码sleep了就可能导致整个系统挂起(hang)
int pthread_spin_destroy(pthread_spinlock_t *);
int pthread_spin_init(pthread_spinlock_t *, int);
int pthread_spin_lock(pthread_spinlock_t *);
int pthread_spin_trylock(pthread_spinlock_t *);
int pthread_spin_unlock(pthread_spinlock_t *);

初始化自旋锁

pthread_spin_init用来申请使用自旋锁所需要的资源并且将它初始化为非锁定状态。pshared的取值及其含义:
PTHREAD_PROCESS_SHARED:该自旋锁可以在多个进程中的线程之间共享。
PTHREAD_PROCESS_PRIVATE:仅初始化本自旋锁的线程所在的进程内的线程才能够使用该自旋锁。

获得一个自旋锁

pthread_spin_lock用来获取(锁定)指定的自旋锁. 如果该自旋锁当前没有被其它线程所持有,则调用该函数的线程获得该自旋锁.否则该函数在获得自旋锁之前不会返回。如果调用该函数的线程在调用该函数时已经持有了该自旋锁,则结果是不确定的。

尝试获取一个自旋锁

pthread_spin_trylock会尝试获取指定的自旋锁,如果无法获取则理解返回失败。

释放(解锁)一个自旋锁

pthread_spin_unlock用于释放指定的自旋锁。

销毁一个自旋锁

pthread_spin_destroy 用来销毁指定的自旋锁并释放所有相关联的资源(所谓的所有指的是由pthread_spin_init自动申请的资源)在调用该函数之后如果没有调用 pthread_spin_init重新初始化自旋锁,则任何尝试使用该锁的调用的结果都是未定义的。如果调用该函数时自旋锁正在被使用或者自旋锁未被初 始化则结果是未定义的。

信号量 semphore

unique_lock

  • std::unique_lock是一个模板类就是lock guard的一种形式。和std::lock_guard一样,声明的时候直接加锁。如果加不上锁就阻塞。
  • C++11多线程 unique_lock详解

成员函数

lock()
unlock()
try_lock()
release()  // 返回它所管理的mutex对象指针,并释放所有权;也就是说,这个unique_lock和mutex不再有关系。

thread local

  1. thread_local是说,每一个线程都会拥有一份自己的thread_local对象,
  2. tcmalloc是在当前调用的线程生成一份threadcache.
  3. 只有如下三种类型被thread_local类修饰
    1. 命名空间下的全局变量
    2. 类的static成员变量
    3. 本地局部变量
  • 线程局部存储空间 pthread_key_t、__thread 即 ThreadLocal
  • c++11: thread_local

__thread

  • __thread是GCC内置的线程局部存储设施,
  • __thread变量每一个线程有一份独立实体,各个线程的值互不干扰。可以用来修饰那些带有全局性且值可能变,但是各线程独立不干扰的变量;
  • 只能修饰POD类型(类似整型指针的标量),不能修饰class类型,因为无法自动调用构造函数和析构函数;
  • 可以用于修饰全局变量,函数内的静态变量,不能修饰函数的局部变量或者class的普通成员变量;
    且__thread变量值只能初始化为编译器常量。

linux多线程同步概览相关推荐

  1. Linux多线程同步的几种方式

    线程的最大特点是资源的共享性,但资源共享中的同步问题是多线程编程的难点.linux下提供了多种方式来处理线程同步,最常用的是互斥锁.条件变量和信号量. 1)互斥锁(mutex) 通过锁机制实现线程间的 ...

  2. Linux多线程同步

    1 互斥锁 互斥锁用来保证一段时间内只有一个线程在执行一段代码.必要性显而易见:假设各个线程向同一个文件顺序写入数据,最后得到的结果一定是灾难性的. 先看下面一段代码.这是一个读/写程序,它们公用一个 ...

  3. Linux多线程同步------条件变量

    先来看下<Linux高性能服务器编程>中对条件变量的描述: 上述话可以总结为: 多线程中某一个线程依赖于另外一个线程对共享数据的改变时,就可以使用条件变量! 用消费者生产者的来理解条件变量 ...

  4. Linux 多线程同步机制:互斥量、信号量、条件变量

    互斥量:互斥量提供对共享资源的保护访问,它的两种状态:lock和unlock,用来保证某段时间内只有一个线程使用共享资源,互斥量的数据类型是pthread_mutex_t 主要涉及函数:pthread ...

  5. Linux多线程同步——信号量

    线程同步 同步主线程与子线程 test.c #include <pthread.h> #include <sys/syscall.h> #include <fcntl.h ...

  6. linux sem_wait sleep,[Linux]多线程同步之sem_wait()学习笔记

    1.semaphore 的这种信号量不仅可用于同一进程的线程同步,也可以用于不同进程间同步. 一个生产者-消费者例子:生产者不停的向一个固定大小的环形队列中添加数据,消费者从环形队列中清零数据,如果生 ...

  7. Linux多线程同步——互斥锁

    互斥锁 当多个线程对同一个资源进行访问的时候,为了这个资源的安全性,我们需要对这个资源进行锁定,规定同一时间只有一个资源能够获得该锁的钥匙,其它线程要获得该资源需要等待该线程 互斥锁创建 pthrea ...

  8. linux 线程间传送消息,Linux 多线程同步-消息队列

    消息队列是消息的链表,存放在内核中并有消息队列标示符标示. msgget用于创建一个新队列或打开一个现存的队列.msgsnd将新消息加入到消息队列中:每个消息包括一个long型的type:和消息缓存: ...

  9. linux 线程同步消息队列,Linux 多线程同步之消息队列

    消息队列是消息的链表,存放在内核中并有消息队列标示符标示. msgget用于创建一个新队列或打开一个现存的队列.msgsnd将新消息加入到消息队列中:每个消息包括一个long型的type:和消息缓存: ...

最新文章

  1. php中转,使用 PHP Curl 做数据中转
  2. SharePoint 2010开发实例精选——“每日一句”WebPart
  3. Linux 使用grep过滤多个条件及grep常用过滤命令
  4. 南加州大学等开源元学习研究库learn2learn
  5. 【Linux】Linux命令大全----常用文件操作命令
  6. 教机器写代码:增强拓扑进化网络(NEAT)
  7. 【博客项目】—用户删除功能(十二)
  8. ORA-01092解决一例
  9. 无法访问hadoop yarn8088端口的解决方法
  10. mysql 命令 字符集_MySQL的字符集操作命令总结
  11. matlab fullbnt,Matlab2010下使用FULLBNT工具箱建立复杂的动态贝叶斯网络
  12. FFmpeg下载无损截取HLS视频流
  13. ar5b97无线网卡驱动linux 版下载,atheros ar5b97驱动
  14. Mac大小写切换,中英文切换
  15. 网络广告计费方式CPM、CPA、CPS、CPT、CPC及比较分析
  16. 将tensorflow与微信小程序结合,微信开发者工具使用tensorflowJS插件出错:Error: Plugin tfjsPlugin has not registered.
  17. 大白菜超级U盘启动制作工具V2.0(网络增强版)
  18. puppet server升级,puppet 常见错误解决
  19. 国家区块链漏洞库《区块链漏洞定级细则》发布
  20. 用python扑克随机发牌_python:扑克牌发牌程序

热门文章

  1. 视觉感知特性【2】一些视觉感知因子
  2. 操作系统原理_田丽华(10)文件系统
  3. 安装:logstash后,爆出:ModuleNotFoundError: No module named ‘event‘,解决方法。
  4. 车路协同路侧全息感知解决方案
  5. C++中string append函数的使用与字符串拼接
  6. 从零开始构建PHP版mud游戏(二)
  7. 暗黑3显示无效的服务器,《暗黑3》为啥爆不出好装备?服务器IP决定你的人品...
  8. android记事本开发背景,Android记事本开发之界面设计
  9. 互联网理财产品有哪些?互联网金融模式知多少?
  10. CamStudiomdash;mdash;优秀免费的屏幕录像软件