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, 出错则返回错误编号.

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

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

目录
相关文章
|
23天前
|
Linux C++
LInux下Posix的传统线程示例
LInux下Posix的传统线程示例
19 1
|
2天前
|
固态存储 Ubuntu Linux
Linux(29) 多线程快速解压缩|删除|监视大型文件
Linux(29) 多线程快速解压缩|删除|监视大型文件
11 1
|
7天前
|
安全 Java 调度
Java并发编程:深入理解线程与锁
【4月更文挑战第18天】本文探讨了Java中的线程和锁机制,包括线程的创建(通过Thread类、Runnable接口或Callable/Future)及其生命周期。Java提供多种锁机制,如`synchronized`关键字、ReentrantLock和ReadWriteLock,以确保并发访问共享资源的安全。此外,文章还介绍了高级并发工具,如Semaphore(控制并发线程数)、CountDownLatch(线程间等待)和CyclicBarrier(同步多个线程)。掌握这些知识对于编写高效、正确的并发程序至关重要。
|
10天前
|
存储 缓存 Java
线程同步的艺术:探索 JAVA 主流锁的奥秘
本文介绍了 Java 中的锁机制,包括悲观锁与乐观锁的并发策略。悲观锁假设多线程环境下数据冲突频繁,访问前先加锁,如 `synchronized` 和 `ReentrantLock`。乐观锁则在访问资源前不加锁,通过版本号或 CAS 机制保证数据一致性,适用于冲突少的场景。锁的获取失败时,线程可以选择阻塞(如自旋锁、适应性自旋锁)或不阻塞(如无锁、偏向锁、轻量级锁、重量级锁)。此外,还讨论了公平锁与非公平锁,以及可重入锁与非可重入锁的特性。最后,提到了共享锁(读锁)和排他锁(写锁)的概念,适用于不同类型的并发访问需求。
40 2
|
11天前
|
Java 程序员 编译器
Java中的线程同步与锁优化策略
【4月更文挑战第14天】在多线程编程中,线程同步是确保数据一致性和程序正确性的关键。Java提供了多种机制来实现线程同步,其中最常用的是synchronized关键字和Lock接口。本文将深入探讨Java中的线程同步问题,并分析如何通过锁优化策略提高程序性能。我们将首先介绍线程同步的基本概念,然后详细讨论synchronized和Lock的使用及优缺点,最后探讨一些锁优化技巧,如锁粗化、锁消除和读写锁等。
|
19天前
|
安全 Java 调度
深入理解Java中的线程安全与锁机制
【4月更文挑战第6天】 在并发编程领域,Java语言提供了强大的线程支持和同步机制来确保多线程环境下的数据一致性和线程安全性。本文将深入探讨Java中线程安全的概念、常见的线程安全问题以及如何使用不同的锁机制来解决这些问题。我们将从基本的synchronized关键字开始,到显式锁(如ReentrantLock),再到读写锁(ReadWriteLock)的讨论,并结合实例代码来展示它们在实际开发中的应用。通过本文,读者不仅能够理解线程安全的重要性,还能掌握如何有效地在Java中应用各种锁机制以保障程序的稳定运行。
|
29天前
|
监控 Linux 调度
【Linux 应用开发 】Linux 下应用层线程优先级管理解析
【Linux 应用开发 】Linux 下应用层线程优先级管理解析
46 0
|
16天前
|
存储 Java 数据库连接
java多线程之线程通信
java多线程之线程通信
|
27天前
|
存储 缓存 NoSQL
Redis单线程已经很快了6.0引入多线程
Redis单线程已经很快了6.0引入多线程
31 3
|
30天前
|
消息中间件 安全 Linux
线程同步与IPC:单进程多线程环境下的选择与权衡
线程同步与IPC:单进程多线程环境下的选择与权衡
58 0