Linux线程互斥是如何实现的,大牛来详细分析

  一、互斥锁

  为啥要有互斥?

  多个进程/线程执行的先后顺序不确定,何时切出CPU也不确定。

  多个进程/线程访问变量的动作往往不是原子的。

  1. 操作步骤

  (1)创建锁

  // 创建互斥锁mutex

  pthread_mutex_t mutex;

  (2)初始化锁

  在Linux下, 线程的互斥量数据类型是pthread_mutex_t 在使用前, 要对它进行初始化:

  初始化的两种方法:(推荐使用第二种)

  1.静态分配

  pthread_mutex mutex = PTHREAD_MUTEX_INITIALIZER;

  2.动态分配

  int pthread_mutex_init(pthread_mutex_t *restrict mutex, const

pthread_mutexattr_t *restrict attr);

  mutex: 要初始化的互斥量(restrict的作用是告诉调用者,不要改变指针的指向)

  attr:锁的属性,一般写NULL

  加restrict的作用:只用于修饰函数参数里的指针,这个指针会频繁使用,所以把这个地址放到寄存器里,用着好找。

  ①设置线程的属性

  int pthread_attr_init(pthread_attr_t *attr);//初始化线程属性

  int pthread_attr_destroy(pthread_attr_t *attr);//销毁线程属性

  Thread attributes(线程属性):

  线程的分离属性: Detach state=PTHREAD_CREATE_DETACHED

  线程的竞争范围: Scope = PTHREAD_SCOPE_SYSTEM

  是否继承调度策略: Inherit scheduler = PTHREAD_EXPLICIT_SCHED

  调度策略: Scheduling policy = SCHED_OTHER

  调度优先级: Scheduling priority = 0

  线程栈之间的保留区域: Guard size = 4096 bytes

  自己指定栈地址: Stack address = 0x40197000

  栈大小: Stack size = 0x3000000 bytes

  //设置线程的分离属性

  int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);

  //detachstate:有以下两种选择

  PTHREAD_CREATE_DETACHED:设置成分离态

  PTHREAD_CREATE_JOINABLE:设置成可结合态

  //获取线程的分离属性

  int pthread_attr_getdetachstate(pthread_attr_t *attr, int

*detachstate);

  //int *detachstate:输出型参数,将分离属性存放在该变量里

  (3)上锁 && 解锁

  对共享资源的访问, 要对互斥量进行加锁, 如果互斥量已经上了锁, 调用线程会阻塞, 直到互斥量被解锁.

在完成了对共享资源的访问后,要对互斥量进行解锁。

  具体说一下trylock函数, 这个函数是非阻塞调用模式, 也就是说, 如果互斥量没被锁住, trylock函数将把互斥量加锁,

并获得对共享资源的访问权限; 如果互斥量被锁住了, trylock函数将不会阻塞等待而直接返回EBUSY, 表示共享资源处于忙状态。

  锁粒度(尽量小):临界区操作个数,执行时间的长短

  int pthread_mutex_lock(pthread_mutex_t *mutex);

  //加锁:如果是1,置0,返回

  // 如果是0,阻塞

  int pthread_mutex_trylock(pthread_mutex_t *mutex);//非阻塞加锁

  int pthread_mutex_unlock(pthread_mutex_t *mutex); //解锁

  返回值: 成功则返回0, 出错则返回错误编号.

  (4)销毁互斥锁

  对于动态分配的互斥量, 在申请内存(malloc)之后, 通过pthread_mutex_init进行初始化,

并且在释放内存(free)前需要调用pthread_mutex_destroy.

  注意:

  使用PTHREAD_MUTEX_INITIALIZER初始化的互斥量不需要销毁

  不要销毁一个已经加锁的互斥量

  已销毁的互斥量要确保后面不会有线程尝试加锁

  pthread_mutex_destroy(&lock); //销毁

  返回值: 成功则返回0, 出错则返回错误编号.

  说明: 如果使用默认的属性初始化互斥量, 只需把attr设为NULL. 其他值在以后讲解。

  2. 死锁

  (1)死锁的两种情况:

  情况1:

  如果两个线程先后调用两次lock,第二次调用lock时,由于锁已被占用,该线程会挂起等待别的线程释放锁,然后锁正是被自己占用着的,该线程又被挂起不能释放锁,因此就永远处于挂起等待状态了,这就叫死锁。

  情况2:

  有线程A、B。A获得锁1,B获得锁2,此时A调用lock企图获得锁2,结果是需要挂起等待B释放锁2,而此时B也调用了lock企图获得锁1,结果是B挂起等待A释放锁1,于是乎A、B永远处于挂起状态。

  (2)避免的死锁的原则

  死锁主要发生在有多个依赖锁存在时,会在一个线程试图以与另一个线程相反顺序锁住互斥量时发生.如何避免死锁是使用互斥量应该格外注意的东西。

  总体来讲, 有几个不成文的基本原则:

  对共享资源操作前一定要获得锁。

  完成操作以后一定要释放锁。

  尽量短时间地占用锁。

  如果有多锁, 如获得顺序是ABC连环扣, 释放顺序也应该是ABC。

  线程错误返回时应该释放它所获得的锁。

  写程序是尽量避免同时获得多个锁,如果一定要这么做,则遵循一个原则:如果所有线程在需要多个锁时都按相同的先后顺序(常见是按mutex变量的地址顺序)获得锁,则不会出现死锁。

  mutex互斥信号量锁住的不是一个变量,而是阻塞住一段程序。如果对一个mutex变量testlock,执行了第一次pthread_mutex_lock(testlock)之后,在unlock之前的这段时间内,如果有其他线程也执行到了pthread_mutex_lock,这个线程就会阻塞住,直到之前的线程unlock之后才能执行,由此,实现同步,也就达到保护临界区资源的目的。

  为了实现互斥操作,大多数体系结构提供了swap或exchange指令,该指令的作用是把寄存器和内存单元的数据交换,由于只有一条指令,保证了原子性。即使是多处理器平台,访问内存的总线周期也有先后,一个处理器的交换指令执行时另一个处理器的交换指令只能等待总线周期。

  (3)临界区代码原则

  短——临界区代码简洁明了;

  平——临界区代码逻辑清晰,没有复杂的函数调用尤其是尽量不要申请其他互斥资源;

  快:临界区代码执行速度快。

  3. 互斥锁和信号量的区别

  互斥量用于线程的互斥,信号线用于线程的同步。

  这是互斥量和信号量的根本区别,也就是互斥和同步之间的区别。

  互斥:是指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的。

  同步:是指在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问。在大多数情况下,同步已经实现了互斥,特别是所有写入资源的情况必定是互斥的。少数情况是指可以允许多个访问者同时访问资源

  互斥量值只能为0/1,信号量值可以为非负整数。

  也就是说,一个互斥量只能用于一个资源的互斥访问,它不能实现多个资源的多线程互斥问题。信号量可以实现多个同类资源的多线程互斥和同步。当信号量为单值信号量是,也可以完成一个资源的互斥访问。

  互斥量的加锁和解锁必须由同一线程分别对应使用,信号量可以由一个线程释放,另一个线程得到。

  4. 线程安全和可重入

  可重入函数:在多个执行流中被同时调用不会存在问题。

  线程安全函数:在多线程中被同时调用不会存在问题。

  可重入函数一般情况下都是线程安全的

  线程安全函数不一定是可重入函数

  二、自旋锁

  1. 操作步骤

  //1. 定义自旋锁

  pthread_spinlock_t spin;

  //2. 初始化自旋锁

  int pthread_spin_init(pthread_spinlock_t *lock, int pshared);

  //3. 上锁

  int pthread_spin_lock(pthread_spinlock_t *lock);

  int pthread_spin_trylock(pthread_spinlock_t *lock);

  //4. 解锁

  int pthread_spin_unlock(pthread_spinlock_t *lock);

  //5. 销毁锁

  int pthread_spin_destroy(pthread_spinlock_t *lock);

  2. 自旋锁和互斥锁的区别

  互斥锁是当阻塞在pthread_mutex_lock时,放弃CPU,好让别人使用CPU。自旋锁阻塞在pthread_spin_lock时,不会释放CPU,不断向cup询问可以用了不。

  三、读写锁

  1. 读写锁的规则

  读读共享

  读写排他

  写写排他

  写优先级高

  2. 操作步骤

  // 1. 定义锁

  pthread_rwlock_t lock;

  // 2. 初始化

  pthread_rwlock_init(&lock, NULL);

  // 3. 读锁

  pthread_rwlock_rdlock(&lock);

  pthread_rwlock_wrlock(&lock);

  // 4. 解锁

  pthread_rwlock_unlock(&lock);

  // 5. 销毁锁

  pthread_rwlock_destroy(&lock);

  3. 代码实现

  #include

  #include

  #include

  #include

  int g_count;

  pthread_rwlock_t rw;

  void* route_read(void* arg)

  {

  int id = (int)arg;

  free(arg);

  while(1)

  {

  pthread_rwlock_rdlock(&rw);

  printf("%d read_pthread : %d\n", id, g_count);

  pthread_rwlock_unlock(&rw);

  usleep(100000);

  }

  }

  void* route_write(void* arg)

  {

  int id = (int)arg;

  free(arg);

  while(1)

  {

  pthread_rwlock_wrlock(&rw);

  printf("%d write_pthread : %d\n", id, ++g_count);

  pthread_rwlock_unlock(&rw);

  usleep(100000);

  }

  }

  int main()

  {

  pthread_t w1, r1, r2, r3;

  pthread_rwlock_init(&rw, NULL);

  int* p = (int*)malloc(sizeof(int));

  *p = 1;

  pthread_create(&w1, NULL, route_write, p);

  int* p1 = (int*)malloc(sizeof(int));

  *p1 = 1;

  pthread_create(&r1, NULL, route_read, p1);

  int* p2 = (int*)malloc(sizeof(int));

  *p2 = 2;

  pthread_create(&r2, NULL, route_read, p2);

  int* p3 = (int*)malloc(sizeof(int));

  *p3 = 3;

  pthread_create(&r3, NULL, route_read, p3);

  pthread_join(w1, NULL);

  pthread_join(r1, NULL);

  pthread_join(r2, NULL);

  pthread_join(r3, NULL);

  pthread_rwlock_destroy(&rw);

  }

  Linux视频学习资料

  、Linux线程互斥的视频资料

  http://www.makeru.com.cn/live/1392_715.html?s=45051

  高版本linux内核移植

  http://www.makeru.com.cn/live/3483_1570.html?s=45051

  linux下的IO模型

  http://www.makeru.com.cn/live/4011_1565.html?s=45051

  Linux多线程编程"陷阱"

  http://www.makeru.com.cn/live/5413_1908.html?s=45051

  linux下进程线程间通信原理解析

  http://www.makeru.com.cn/live/3485_1591.html?s=45051

  Linux开发必备:TCP编程

  http://www.makeru.com.cn/live/3485_1633.html?s=45051

  Linux线程互斥

  http://www.makeru.com.cn/live/1392_715.html?s=45051

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,602评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,442评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,878评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,306评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,330评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,071评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,382评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,006评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,512评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,965评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,094评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,732评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,283评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,286评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,512评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,536评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,828评论 2 345