• Linux多线程同步方式


          当多个线程共享相同的内存时,需要确保每个线程看到一致的数据视图,当多个线程同时去修改这片内存时,就可能出现偏差,得到与预期不符合的值。为啥需要同步,一件事情逻辑上一定是有序的,即使在并发环境下;而操作系统对于多线程不会自动帮我们串行化,所以需要我们通过操作系统提供的同步方式api,结合自己的业务逻辑,利用多线程提高性能的同时,保证业务逻辑的正确性。一般而言,linux下同步方式主要有4种,原子锁,互斥量,读写锁和条件变量。下面一一介绍几种同步方式。

    1. spinlock

    1)  概念

    spinlock是一种互斥结构,通过CPU提供的特殊的原子指令集合实现互斥地访问一个资源,需要硬件支持。一个线程访问数据未结束的时候,其他线程不得对同一个数据进行访问。

    2)  实现

    spinlock一般基于原子的read-modify-write操作实现。read-modify-write操作允许一个CPU读取一个值,修改该值,并将修改完成的值回写内存的三个操作作为一个原子总线操作,因此需要CPU特殊支持。具体而言,通过test-and-set指令实现,从内存中读取一个值,然后和0比较,并且将内存中的值设置为1。

    3) 相关函数

       a) 原子操作

    1   test_and_set(volatile int* addr, value)
    2   {           
    3         return os_atomic_test_and_set_int(addr,value);
    4    }

       这里volatile修饰词告诉编译器从内存中获取,保证正确性,避免从寄存器中读取到不准确的值。

       b) 设置锁变量

     1 set_spinlock(lock_word)
     2 {
     3          int i = 0;
     4          int value;
     5          while (true)
     6          {
     7               value = test_and_set(&lock_word, 1);
     8               if (value == 0) //尚未被占用,可以获取
     9                      break;
    10               else   //已经被其他线程占用,继续轮转
    11                     do nothing
    12          }
    13 }

    c)       重置锁变量

    1 reset_spinlock(lock_word)
    2 {
    3    test_and_set (lock_word, 0);
    4 } 

    2.  mutex

       1) 概念

       与 spinlock作用相同,保证互斥地访问一个资源。

       2) 相关函数

       int pthread_mutex_lock(pthread_mutex_t *mutex)

       int pthread_mutex_trylock(pthread_mutex_t *mutex)

       int pthread_mutex_unlock(pthread_mutex_t *mutex)

       pthread_mutex_trylock()语义与pthread_mutex_lock()类似,不同的是在锁已经被占据时返回EBUSY而不是挂起等待。

       3)  spinlock与mutex的区别

          a) 作用范围,mutex是内核对象,可以在多线程,多进程同步中使用;spinlock作用范围仅限于本进程(锁变量是进程内的),仅适用于多线程同步。

          b) spinlock依赖于硬件的原子操作指令

          c) 线程获取spinlock失败时,会采取循环等待的方式,此时线程处于运行状态,CPU空转;而获取mutex失败时,线程会挂起,线程处于wait状态,不会被内核调度。

         d) 由于3的特点,进入等待状态或从等待状态被唤醒,都涉及到CPU的上下文切换,而CPU切换是比较耗时的,一般需要25us。相对而言spinlock则没有这样的代价,效率更高。

         e) 也由于3的特点,spinlock会空转,导致浪费大量的CPU时间片,若用户持有锁时间长,导致空转时间长,也得不偿失。因此spinlock比较适合于“快拿快放”的使用场景。

    3.读写锁

    1) 概念

    spinlock和互斥量都是保证同一时刻只有一个线程操作共享内存。互斥锁要么是加锁状态,要么是不加锁状态,一次只有一个线程可以对其加锁。读写所可以有3种状态,读模式下加锁状态,写模式下加锁状态,不加锁状态。相对于前两者,读写锁有更高的并发度,允许多个线程同时读共享内存。

    2) 相关函数

    pthread_rwlock_rdlock(pthread_rwlock_t*);  读锁定

    pthread_rwlock_tryrdlock(pthread_rwlock_t*); 非阻塞读锁定

    pthread_rwlock_wrlock(pthread_rwlock_t*); 写锁定

    pthread_rwlock_trywrlock(pthread_rwlock_t*); 非阻塞写锁定

    pthread_rwlock_unlock(pthread_rwlock_t*);  释放锁

     

    4. 条件变量

    1) 概念

    条件变量是另外一种同步机制,通过与互斥锁配合使用,利用锁保护条件变量,通过条件变量实现唤醒和等待的机制。通过这种方式,允许线程以无竞争的方式等待特定的条件发生

    2) 相关函数

    int pthread_cond_signal(pthread_cond_t *cond);  //唤醒等待条件某个线程

    int pthread_cond_broadcast(pthread_cond_t *cond); //唤醒等待条件所有线程

    int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex); //等待条件发生。 

    3) 说明

    调用pthread_cond_wait之前,需要线程获取互斥量,调用者把互斥量传递给函数,函数把调用线程发到等待队列上,然后对互斥量解锁,这个操作是原子操作。当pthread_cond_wait返回时,互斥量会再次被锁住,这个实现都在pthread_cond_wait函数中实现,不需要用户逻辑介入。

  • 相关阅读:
    Linux下压缩文件和解压缩
    原生JS随机数
    Git问题-Git warning LF will be replaced by CRLF
    mysql进程占用cpu居高不下处理
    mysql修改查询的结果包含的敏感字
    mysql 全库备份和还原
    mysql创建用户并设置权限
    Mysql-开启 SSL加密 mysql_ssl_rsa_setup
    tp6_005控制器
    ERROR 1104 (42000): The SELECT would examine more than MAX_JOIN_SIZE rows; check your WHERE and use SET SQL_BIG_SELECTS=1 or SET MAX_JOIN_SIZE=# if the SELECT is okay
  • 原文地址:https://www.cnblogs.com/cchust/p/4252500.html
Copyright © 2020-2023  润新知