问答文章1 问答文章501 问答文章1001 问答文章1501 问答文章2001 问答文章2501 问答文章3001 问答文章3501 问答文章4001 问答文章4501 问答文章5001 问答文章5501 问答文章6001 问答文章6501 问答文章7001 问答文章7501 问答文章8001 问答文章8501 问答文章9001 问答文章9501

如何对读写锁进行处理

发布网友 发布时间:2022-04-28 12:02

我来回答

2个回答

懂视网 时间:2022-05-06 03:19

最近碰到一个问题,线上一台机器在等待信号量时间过长,mysql的监控线程认为此时mysqld已经hang住了,于是自杀重启。这里涉及到一

最近碰到一个问题,线上一台机器在等待信号量时间过长,,mysql的监控线程认为此时mysqld已经hang住了,于是自杀重启。这里涉及到一个有趣的问题,也就是mysql如何对读写锁进行处理。

主要包括三个部分:

1. 建锁

2. 加锁

3. 解锁

4. 监控锁

以下内容基于Percona5.5.18进行分析

1.创建锁

锁的创建实际上就是初始化一个RW结构体(rw_lock_t),实际调用函数如下:

[cpp]

  • # define rw_lock_create(K, L, level)
  • rw_lock_create_func((L),#L)
  • 在rw_lock_create上有三个参数,在实际场景锁时只用到第2个参数

    其中K表示mysql_pfs_key_t,level显示当前的操作类型(起码看起来是的,在文件sync0sync.h中定义),看起来k是为performance schema准备的,而k代表了当前操作所在的层次。

    例如:purge线程的读写锁创建:

    [html]

  • rw_lock_create(trx_purge_latch_key,
  • 我们进去rw_lock_create_func看看到底是怎么创建的。

    可以看到这个函数的逻辑其实很简单:

    lock->lock_word =X_LOCK_DECR; //关键字段

    用于限制读写锁的最大并发数,代码里的注释如下:

    [cpp]

    在尝试加锁时会调用rw_lock_lock_word_decr减少lock_word

    在初始化一系列变量后,执行:

    [cpp]

  • lock->event = os_event_create(NULL);
  • lock->wait_ex_event = os_event_create(NULL);
  • os_event_create用于创建一个系统信号,实际上最终创建的还是互斥量(os_fast_mutex_init(&(event->os_mutex));以及条件变量(os_cond_init(&(event->cond_var));)

    最后将lock加入到全局链表rw_lock_list中

    linux

    热心网友 时间:2022-05-06 00:27

    信号量强调的是线程(或进程)间的同步:“信号量用在多线程多任务同步的,一个线程完成了某一个动作就通过信号量告诉别的线程,别的线程再进行某些动作(大家都 在sem_wait的时候,就阻塞在那里)。当信号量为单值信号量是,也可以完成一个资源的互斥访问。

    有名信号量:可以用于不同进程间或多线程间的互斥与同步

    创建打开有名信号量

    sem_t *sem_open(const char *name, int oflag);

    sem_t *sem_open(const char *name, int oflag, mode_t mode, unsigned int value);

    成功返回信号量指针;失败返回SEM_FAILED,设置errnoname是文件路径名,但不能写成/tmp/a.sem这样的形式,因为在linux下,sem都是在/dev/shm目录下,可写成"/mysem"或"mysem",创建出来的文件都 是"/dev/shm/sem.mysem",mode设置为0666,value设置为信号量的初始值.所需信号灯等已存在条件下指定O_CREAT|O_EXCL却是个错误。

    关闭信号量,进程终止时,会自动调用它

    int sem_close(sem_t *sem);

    成功返回0;失败返回-1,设置errno

    删除信号量,立即删除信号量名字,当其他进程都关闭它时,销毁它

    int sem_unlink(const char *name);

    等待信号量,测试信号量的值,如果其值小于或等于0,那么就等待(阻塞);一旦其值变为大于0就将它减1,并返回

    int sem_wait(sem_t *sem);

    int sem_trywait(sem_t *sem);

    成功返回0;失败返回-1,设置errno

    当信号量的值为0时,sem_trywait立即返回,设置errno为EAGAIN。如果被某个信号中断,sem_wait会过早地返回,设置errno为EINTR

    发出信号量,给它的值加1,然后唤醒正在等待该信号量的进程或线程

    int sem_post(sem_t *sem);

    成功返回0;失败返回-1,不会改变它的值,设置errno,该函数是异步信号安全的,可以在信号处理程序里调用它无名信号量,用于进程体内各线程间的互斥和同步,使用如下API(无名信号量,基于内存的信号量)

    (1)、sem_init

    功能:用于创建一个信号量,并初始化信号量的值。

    头文件:

    函数原型: int sem_init (sem_t* sem, int pshared, unsigned int value);

    函数传入值: sem:信号量。pshared:决定信号量能否在几个进程间共享。由于目前LINUX还没有实现进程间共享信息量,所以这个值只能取0。

    (2)其他函数。

    int sem_wait (sem_t* sem);

    int sem_trywait (sem_t* sem);

    int sem_post (sem_t* sem);

    int sem_getvalue (sem_t* sem);

    int sem_destroy (sem_t* sem);

    功能:sem_wait和sem_trywait相当于P操作,它们都能将信号量的值减一,两者的区别在于若信号量的值小于零时,sem_wait将会阻塞进程,而sem_trywait则会立即返回。sem_post相当于V操作,它将信号量的值加一,同时发出唤醒的信号给等待的进程(或线程)。

    sem_getvalue 得到信号量的值。

    sem_destroy 摧毁信号量。

    如果某个基于内存的信号灯是在不同进程间同步的,该信号灯必须存放在共享内存区中,这要只要该共享内存区存在,该信号灯就存在。

    互斥锁(又名互斥量)强调的是资源的访问互斥:互斥锁是用在多线程多任务互斥的,一个线程占用了某一个资源,那么别的线程就无法访问,直到这个线程unlock,其他的线程才开始可以利用这个资源。比如对全局变量的访问,有时要加锁,操作完了,在解锁。有的时候锁和信号量会同时使用的”

    也就是说,信号量不一定是锁定某一个资源,而是流程上的概念,比如:有A,B两个线程,B线程要等A线程完成某一任务以后再进行自己下面的步骤,这个任务并不一定是锁定某一资源,还可以是进行一些计算或者数据处理之类。而线程互斥量则是“锁住某一资源”的概念,在锁定期间内,其他线程无法对被保护的数据进行操作。在有些情况下两者可以互换。

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

    头文件:

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

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

    首先说一下加锁函数:

    头文件:

    int pthread_mutex_lock(pthread_mutex_t *mutex);

    int pthread_mutex_trylock(pthread_mutex_t *mutex);

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

    说 明: 具体说一下trylock函数, 这个函数是非阻塞调用模式, 也就是说, 如果互斥量没被锁住, trylock函数将把互斥量加锁, 并获得对共享资源的访问权限; 如果互斥量 被锁住了, trylock函数将不会阻塞等待而直接返回EBUSY, 表示共享资源处于忙状态.

    再说一下解所函数:

    头文件:

    原型: int pthread_mutex_unlock(pthread_mutex_t *mutex);

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

    条件变量常与互斥锁同时使用,达到线程同步的目的:条件变量通过允许线程阻塞和等待另一个线程发送信号的方法弥补了互斥锁的不足。在发 送信号时,如果没有线程 等待在该条件变量上,那么信号将丢失;而信号量有计数值,每次信号量post操作都会被记录

    互斥锁必须是谁上锁就由谁来解锁,而信号量的wait和post操作不必由同一个线程执行。

    2. 互斥锁要么被锁住,要么被解开,和二值信号量类似

    3. sem_post是各种同步技巧中,唯一一个能在信号处理程序中安全调用的函数

    4. 互斥锁是为上锁而优化的;条件变量是为等待而优化的; 信号量既可用于上锁,也可用于等待,因此会有更多的开销和更高的复杂性

    5. 互斥锁,条件变量都只用于同一个进程的各线程间,而信号量(有名信号量)可用于不同进程间的同步。当信号量用于进程间同步时,要求信号量建立在共享内存区。

    6. 信号量有计数值,每次信号量post操作都会被记录,而条件变量在发送信号时,如果没有线程在等待该条件变量,那么信号将丢失。

    读写锁

    读写锁与互斥量类似,不过读写锁允许更高的并行性。互斥量要么是锁住状态要么是不加锁状态,而且一次只有一个线程可以对其加锁。

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

    锁。

    在读写锁是写加锁状态时,在这个锁被解锁之前,所有试图对这个锁加锁的线程都会被阻塞。当读写锁在读加锁状态时,所有试图以读模式对它进行加锁的线程都可以得到访问权,但是如果线程希望以写模式对此锁进行加锁,它必须阻塞直到所有的线程释放读锁。虽然读写锁的实现各不相同,但当读写锁处于读模式锁住状态时,如果有另外的线程试图以写模式加锁,读写锁通常会阻塞随后的读模式锁请求。这样可以避免读模式锁长期占用,而等待的写模式锁请求一直得不到满足。

    读写锁非常适合于对数据结构读的次数远大于写的情况。当读写锁在写模式下时,它所保护的数据结构就可以被安全地修改,因为当前只有一个线程可以在写模式下拥 有这个锁。当读写锁在读状态下时,只要线程获取了读模式下的读写锁,该锁所保护的数据结构可以被多个获得读模式锁的线程读取。

    读写锁也叫做共享-独占锁,当读写锁以读模式锁住时,它是以共享模式锁住的;当他以写模式锁住时,它是以独占模式锁住的。

    初始化和销毁:

    #include

    int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);

    int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);

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

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

    读和写:

    #include

    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

    int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);

    int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);

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

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

    虽然读写锁提高了并行性,但是就速度而言并不比互斥量快.

    可能这也是即使有读写锁存在还会使用互斥量的原因,因为他在速度方面略胜一筹。这就需要我们在写程序的时候综合考虑速度和并行性并找到一个折中。

    比如: 假设使用互斥量需要0.5秒,使用读写锁需要0.8秒。在类似学生管理系统这类中,可能百分之九十的时间都是查询操作,那么假如现在突然来个个20个请求,如果使用的是互斥量,那么最后的那个查询请求被满足需要10后。这样,估计没人能受得了。而使用读写锁,应为 读锁能够多次获得。所以所有的20个请求,每个请求都能在1秒左右得到满足。

    也就是说,在一些写操作比较多或是本身需要同步的地方并不多的程序中我们应该使用互斥量,而在读操作远大于写操作的一些程序中我们应该使用读写锁来进行同步

    条件变量(condition)

    条件变量与互斥量一起使用时,允许线程以无竞争的方式等待特定的条件发生。

    条件本身是由互斥量保护的。线程在改变条件状态前必须首先锁住互斥量,其它线程在获得互斥量之前不会察觉到这种改变,因此必须锁定互斥量以后才能计算条件。

    条件的检测是在互斥锁的保护下进行的。如果一个条件为假,一个线程自动阻塞,并释放等待状态改变的互斥锁。如果另一个线程改变了条件,它发信号给关联的条件

    变量,唤醒一个或多个等待它的线程,重新获得互斥锁,重新评价条件。如果两进程共享可读写的内存,条件变量可以被用来实现这两进程间的线程同步。

    1. 初始化:

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

    静态: 可以把常量PTHREAD_COND_INITIALIZER给静态分配的条件变量.

    动态: pthread_cond_init函数, 是释放动态条件变量的内存空间之前, 要用pthread_cond_destroy对其进行清理.

    #include

    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

    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函数的返回并不意味着条件的值一定发生了变化,必须重新检查条件的值。

    pthread_cond_wait函数返回时,相应的互斥锁将被当前线程锁定,即使是函数出错返回。

    阻塞在条件变量上的线程被唤醒以后,直到pthread_cond_wait()函数返回之前条件的值都有可能发生变化。所以函数返回以后,在锁定相应的互斥锁之前,必须重新测试条 件值。最好的测试方法是循环调用pthread_cond_wait函数,并把满足条件的表达式置为循环的终止条件。如:

    pthread_mutex_lock();

    while (condition_is_false)

    pthread_cond_wait();

    pthread_mutex_unlock();

    阻塞在同一个条件变量上的不同线程被释放的次序是不一定的。

    注意:pthread_cond_wait()函数是退出点,如果在调用这个函数时,已有一个挂起的退出请求,且线程允许退出,这个线程将被终止并开始执行善后处理函数,而这时和条 件变量相关的互斥锁仍将处在锁定状态。

    pthread_cond_timedwait函数到了一定的时间,即使条件未发生也会解除阻塞。这个时间由参数abstime指定。函数返回时,相应的互斥锁往往是锁定的,即使是函数出错返回。

    注意:pthread_cond_timedwait函数也是退出点。

    超时时间参数是指一天中的某个时刻。使用举例:

    pthread_timestruc_t to;

    to.tv_sec = time(NULL) + TIMEOUT;

    to.tv_nsec = 0;

    超时返回的错误码是ETIMEDOUT。

    3. 通知条件:

    #include

    int pthread_cond_signal(pthread_cond_t *cond);

    int pthread_cond_broadcast(pthread_cond_t *cond);

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

    这两个函数用于通知线程条件已经满足. 调用这两个函数, 也称向线程或条件发送信号. 必须注意, 一定要在改变条件状态以后再给线程发送信号.

    声明声明:本网页内容为用户发布,旨在传播知识,不代表本网认同其观点,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。E-MAIL:11247931@qq.com
    玉米仁子饭产自哪里 中国期货交易所的交易品种有哪些? 历史要怎么读,有啥诀窍 高中历史诀窍 年终会活动策划方案 深度解析:第一财经回放,探索财经新风向 逆水寒手游庄园怎么邀请好友同住 逆水寒手游 逆水寒不同区可以一起组队吗? 逆水寒手游 逆水寒怎么进入好友世界? 逆水寒手游 逆水寒怎么去别人的庄园? 如果修改QQ密码时忘了问题怎么办? QQ改密码忘记了怎么办 丰田凯美瑞一键启动正确用法? 修改qq密码忘记以前密码了怎么办 一键启动的,怎么样正确启动,熄火 QQ修改密码后忘记了,怎么办 ETF基金如何购买呢?在哪里购买呢?求大侠指点,我想买些 汽车如何一键启动?汽车正确一键启动步骤? 请问一下如何购买etf基金? 一键启动的正确操作方法是什么? QQ改密码的问题忘记了怎么办? 平时登录爱拍网都是QQ登录的,爱拍密码压根网了是什么,求爱拍密码是什么 一键启动自动挡点火正确方法 爱拍我用QQ登录的。忘记密码了怎么办 我在爱拍用QQ号登陆,在拍大师用QQ号的爱拍号,那么密码是什么呢?求高手解答啊。。。 新轩逸一键启动怎么用,轩逸一键启动正确步骤 爱拍的密码忘了怎么办? 爱拍id密码忘记了怎么办 中日ETF互通产品正式上市,怎么购买?收益如何?投资攻略看过来 汽车一键启动正确步骤 谁给我解释下fcntl.h头文件,拭目以待! linux的头文件中是否有writeError函数? 谁知道<fcnt1.h>起什么作用? 通信的方式有多种,假设需要在Linux系 lockf 函数的用法 刚学多线程的一点小问题 EACCES or EAGAIN 在哪个头文件 在css中background-attachment: fixed的意思是啥 fork的分叉函数 如何判断socket已经断开 如何测试创建线程的开销的数量级 如何使用Go建开发高负载WebSocket服务器 高手进,关于C语言在windows上建立多线程的问题(VC6.0上实现) C语言中unix_error在哪个头文件中? 信号量,互斥锁,读写锁和条件变量的区别 线程同步:何时互斥锁不够,还需要条件变量 表示不能在一起的伤感句子有哪些? 不能在一起心酸的句子 不能在一起心酸的句子有哪些? 爱一个人,却又不能在一起的伤感段落