LINUX多线程互斥量和读写锁区别

简介: 线程的同步, 发生在多个线程共享相同内存的时候, 这时要保证每个线程在每个时刻看到的共享数据是一致的.

线程的同步, 发生在多个线程共享相同内存的时候, 这时要保证每个线程在每个时刻看到的共享数据是一致的. 如果每个线程使用的变量都是其他线程不会使用的(read & write), 或者变量是只读的, 就不存在一致性问题. 但是, 如果两个或两个以上的线程可以read/write一个变量时, 就需要对线程进行同步, 以确保它们在访问该变量时, 不会得到无效的值, 同时也可以唯一地修改该变量并使它生效.

  以上就是我们所说的线程同步.

  线程同步有三种常用的机制: 互斥量(mutex) ,读写锁(rwlock)和条件变量(cond).

  互斥量有两种状态:lock和unlock, 它确保同一时间只有一个线程访问数据;

  读写锁有三种状态: 读加锁, 写加锁, 不加锁, 只有一个线程可以占有写模式的读写锁, 但是可以有多个线程同时占有读模式的读写锁.

  条件变量则给多个线程提供了一个会合的场所, 与互斥量一起使用时, 允许线程以无竞争的方式等待特定条件的发生.

  互斥量

  互斥量从本质上说就是一把锁, 提供对共享资源的保护访问.

  1. 初始化:

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

  对于静态分配的互斥量, 可以把它设置为PTHREAD_MUTEX_INITIALIZER, 或者调用pthread_mutex_init.

  对于动态分配的互斥量, 在申请内存(malloc)之后, 通过pthread_mutex_init进行初始化, 并且在释放内存(free)前需要调用pthread_mutex_destroy.

  原型:int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restric attr);int pthread_mutex_destroy(pthread_mutex_t *mutex);头文件: <pthread.h>返回值: 成功则返回0, 出错则返回错误编号.说明: 如果使用默认的属性初始化互斥量, 只需把attr设为NULL. 其他值在以后讲解.

  2. 互斥操作:

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

  首先说一下加锁函数:

  头文件: <pthread.h>原型:int pthread_mutex_lock(pthread_mutex_t *mutex);int pthread_mutex_trylock(pthread_mutex_t *mutex);返回值: 成功则返回0, 出错则返回错误编号.说 明: 具体说一下trylock函数, 这个函数是非阻塞调用模式, 也就是说, 如果互斥量没被锁住, trylock函数将把互斥量加锁, 并获得对共享资源的访问权限; 如果互斥量被锁住了, trylock函数将不会阻塞等待而直接返回EBUSY, 表示共享资源处于忙状态.

  再说一下解所函数:

  头文件: <pthread.h>原型: int pthread_mutex_unlock(pthread_mutex_t *mutex);返回值: 成功则返回0, 出错则返回错误编号.

  3. 死锁:

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

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

  a: 对共享资源操作前一定要获得锁.    b: 完成操作以后一定要释放锁.    c: 尽量短时间地占用锁.    d: 如果有多锁, 如获得顺序是ABC连环扣, 释放顺序也应该是ABC.    e: 线程错误返回时应该释放它所获得的锁. 读写锁

  在线程同步系列的第一篇文章里已经说过, 读写锁是因为有3种状态, 所以可以有更高的并行性.

  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);

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

  同互斥量一样, 在释放读写锁占用的内存之前, 需要先通过pthread_rwlock_destroy对读写锁进行清理工作, 释放由init分配的资源.

  4. 读和写:

  #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);

  成功则返回0, 出错则返回错误编号.这3个函数分别实现获取读锁, 获取写锁和释放锁的操作. 获取锁的两个函数是阻塞操作, 同样, 非阻塞的函数为: #include <pthread.h>

  int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);

  int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);

  成功则返回0, 出错则返回错误编号.非阻塞的获取锁操作, 如果可以获取则返回0, 否则返回错误的EBUSY.

  条件变量

  条件变量分为两部分: 条件和变量. 条件本身是由互斥量保护的. 线程在改变条件状态前先要锁住互斥量.

  1. 初始化:

  条件变量采用的数据类型是pthread_cond_t, 在使用之前必须要进行初始化, 这包括两种方式:

  静态: 可以把常量PTHREAD_COND_INITIALIZER给静态分配的条件变量.动态: pthread_cond_init函数, 是释放动态条件变量的内存空间之前, 要用pthread_cond_destroy对其进行清理. #include <pthread.h>

  int pthread_cond_init(pthread_cond_t *restrict cond, pthread_condattr_t *restrict attr);

  int pthread_cond_destroy(pthread_cond_t *cond);

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

  当pthread_cond_init的attr参数为NULL时, 会创建一个默认属性的条件变量; 非默认情况以后讨论.

  2. 等待条件:

  #include <pthread.h>

  int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restric mutex);

  int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict timeout);

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

  这两个函数分别是阻塞等待和超时等待.

  等待条件函数等待条件变为真, 传递给pthread_cond_wait的互斥量对条件进行保护, 调用者把锁住的互斥量传递给函数. 函数把调用线程放到等待条件的线程列表上, 然后对互斥量解锁, 这两个操作是原子的. 这样便关闭了条件检查和线程进入休眠状态等待条件改变这两个操作之间的时间通道, 这样线程就不会错过条件的任何变化.

  注意:pthread_cond_wait函数内部是先解锁,等待一段时间后判断条件是否满足。若满足,则再加锁,执行数据操作;  若不满足,则继续等待。当pthread_cond_wait返回时, 互斥量再次被锁住.

  实例:

  apr_thread_mutex_lock(m_mutex);

  while (m_queue.empty())

  {

  //线程睡眠解锁,等待唤醒

  apr_thread_cond_wait(m_cond,m_mutex);

  }

  if(m_queue.empty())

  {

  apr_thread_mutex_unlock(m_mutex);

  return false;

  }

  item = m_queue.front();  ///取头元素

  m_queue.pop_front();     ///弹出头元素

  apr_thread_mutex_unlock(m_mutex);

  3. 通知条件:

  #include <pthread.h>

  int pthread_cond_signal(pthread_cond_t *cond);

  int pthread_cond_broadcast(pthread_cond_t *cond);

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

  这两个函数用于通知线程条件已经满足. 调用这两个函数, 也称向线程或条件发送信号.

  必须注意, 一定要在改变条件状态以后再给线程发送信号.

目录
相关文章
【Linux进程概念】—— 操作系统中的“生命体”,计算机里的“多线程”
在计算机系统的底层架构中,操作系统肩负着资源管理与任务调度的重任。当我们启动各类应用程序时,其背后复杂的运作机制便悄然展开。程序,作为静态的指令集合,如何在系统中实现动态执行?本文带你一探究竟!
【Linux进程概念】—— 操作系统中的“生命体”,计算机里的“多线程”
Linux内核中的线程和进程实现详解
了解进程和线程如何工作,可以帮助我们更好地编写程序,充分利用多核CPU,实现并行计算,提高系统的响应速度和计算效能。记住,适当平衡进程和线程的使用,既要拥有独立空间的'兄弟',也需要在'家庭'中分享和并行的成员。对于这个世界,现在,你应该有一个全新的认识。
109 67
|
22天前
|
windows下和linux下cmake的规则有区别吗
通过合理使用CMake的条件逻辑和平台特定的配置选项,开发者可以编写更加灵活和健壮的CMake脚本,确保项目在Windows和Linux上的一致性和可移植性。
113 76
|
2月前
|
linux syscall和int 80的区别
通过以上内容,希望您能更清晰地理解 `int 0x80` 和 `syscall` 的区别及其在不同系统架构中的应用。
231 99
Linux中yum、rpm、apt-get、wget的区别,yum、rpm、apt-get常用命令,CentOS、Ubuntu中安装wget
通过本文,我们详细了解了 `yum`、`rpm`、`apt-get`和 `wget`的区别、常用命令以及在CentOS和Ubuntu中安装 `wget`的方法。`yum`和 `apt-get`是高层次的包管理器,分别用于RPM系和Debian系发行版,能够自动解决依赖问题;而 `rpm`是低层次的包管理工具,适合处理单个包;`wget`则是一个功能强大的下载工具,适用于各种下载任务。在实际使用中,根据系统类型和任务需求选择合适的工具,可以大大提高工作效率和系统管理的便利性。
203 25
Python GIL(全局解释器锁)机制对多线程性能影响的深度分析
在Python开发中,GIL(全局解释器锁)一直备受关注。本文基于CPython解释器,探讨GIL的技术本质及其对程序性能的影响。GIL确保同一时刻只有一个线程执行代码,以保护内存管理的安全性,但也限制了多线程并行计算的效率。文章分析了GIL的必要性、局限性,并介绍了多进程、异步编程等替代方案。尽管Python 3.13计划移除GIL,但该特性至少要到2028年才会默认禁用,因此理解GIL仍至关重要。
247 16
Python GIL(全局解释器锁)机制对多线程性能影响的深度分析
|
2月前
|
Linux编程: 在业务线程中注册和处理Linux信号
本文详细介绍了如何在Linux中通过在业务线程中注册和处理信号。我们讨论了信号的基本概念,并通过完整的代码示例展示了在业务线程中注册和处理信号的方法。通过正确地使用信号处理机制,可以提高程序的健壮性和响应能力。希望本文能帮助您更好地理解和应用Linux信号处理,提高开发效率和代码质量。
60 17
|
2月前
|
Linux编程: 在业务线程中注册和处理Linux信号
通过本文,您可以了解如何在业务线程中注册和处理Linux信号。正确处理信号可以提高程序的健壮性和稳定性。希望这些内容能帮助您更好地理解和应用Linux信号处理机制。
65 26
linux m、mm、mmm函数和make的区别
通过理解和合理使用这些命令,可以更高效地进行项目构建和管理,特别是在复杂的 Android 开发环境中。
127 18
【JavaEE“多线程进阶”】——各种“锁”大总结
乐/悲观锁,轻/重量级锁,自旋锁,挂起等待锁,普通互斥锁,读写锁,公不公平锁,可不可重入锁,synchronized加锁三阶段过程,锁消除,锁粗化
AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等