Linux并发和竞争简记

一. 并发与竞争

  1. Linux系统并发(访问同一个共享资源)产生的原因

    • 多线程并发访问
    • 抢占式并发访问,调度程序可以在任意时刻抢占正在运行的线程
    • 中断服务程序的并发访问
    • SMP(多核)核间并发访问
  2. Linux并发和竞争的几种解决方法:原子操作自旋锁信号量互斥体

二. 原子操作:不能再进一步分割的操作,一般用于变量或位操作

  1. Linux内核使用atomic_t 的结构体来完成整型数据的原子操作,该结构体定义在源码目录include/linux/types.h

     typedef struct { 
     	int counter;
     } atomic_t;
    
  2. 相关API函数在这里插入图片描述

  3. 整型数据原子操作的示例代码

     atomic_t val = ATOMIC_INIT(10);	/* 定义并初始化原子变量 val = 10 */
     atomic_set(&val,100);				/* 设置 val = 100 */ 
     atomic_read(&val);					/* 读取 val 的值 */ 
     atomic_inc(&val);					/* val的值加 1 */
    
  4. 原子位操作,是直接对内存进行操作的

     void set_bit(int nr, void *p);    //将 p 地址的第 nr 位置 1
     void clear_bit(int nr,void *p);   //将 p 地址的第 nr 位清零
     void change_bit(int nr, void *p); //将 p 地址的第 nr 位进行翻转。
     int test_bit(int nr, void *p);			//获取 p 地址的第 nr 位的值
     /* 将 p 地址的第 nr 位置 1,并且返回 nr 位原来的值 */
     int test_and_set_bit(int nr, void *p);
     /* 将 p 地址的第 nr 位清零,并且返回 nr 位原来的值 */
     int test_and_clear_bit(int nr, void *p);
     /* 将 p 地址的第 nr 位翻转,并且返回 nr 位原来的值 */
     int test_and_change_bit(int nr, void *p);
    

三. 自旋锁

  1. 一个线程要访问某个共享资源时,需要先获取相应的;若没有则会循环尝试获取该锁(自旋),直到有另一线程释放锁。

  2. 存在的问题

    • 当线程处于自旋时,会消耗处理器时间,降低系统性能,所以一般自旋锁持有时间不能太长。
    • 自旋锁会自动禁止内核抢占。当线程A得到自旋锁时,会禁止内核抢占;若此时线程A进入休眠状态,线程B需要得到自旋锁;此时线程B得不到自旋锁,也无法调度则线程A无法运行,形成死锁
    • 当线程A得到自旋锁时,发生中断B,中断B服务函数需要自旋锁;此时线程A无法得到运行不能释放锁,中断B得不到锁一直自旋,形成死锁
  3. Linux内核使用结构体spinlock_t 表示自旋锁,该结构体定义在源码目录include/linux/spinlock_types.h

     typedef struct spinlock {
     	union { 
             struct raw_spinlock rlock;
     #ifdef CONFIG_DEBUG_LOCK_ALLOC 
     #define LOCK_PADSIZE (offsetof(struct raw_spinlock, dep_map))
     		struct { 
                 u8 __padding[LOCK_PADSIZE];
                 struct lockdep_map dep_map;
             };
     #endif 
     	};
     } spinlock_t;
    
  4. 自旋锁相关API函数 — 不考虑中断

     DEFINE_SPINLOCK(spinlock_t lock); 		//定义并初始化一个自选变量
     int spin_lock_init(spinlock_t *lock);	//初始化自旋锁
     void spin_lock(spinlock_t *lock);		//获取指定的自旋锁,也叫做加锁
     void spin_unlock(spinlock_t *lock);		//释放指定的自旋锁
     int spin_trylock(spinlock_t *lock);		//尝试获取指定的自旋锁,如果没有获取到
     										//就返回 0
     int spin_is_locked(spinlock_t *lock);	//检查指定的自旋锁是否被获取,如果没有被										 //获取就 返回非 0,否则返回 0
    
  5. 自旋锁相关API函数 — 考虑中断

     /* 保存中断状态,禁止本地中断,并获取自旋锁 */
     void spin_lock_irqsave(spinlock_t *lock, unsigned long flags);
     /* 将中断状态恢复到以前的状态,并且激活本地中断,释放自旋锁 */
     void spin_unlock_irqrestore(spinlock_t *lock, unsigned long flags);
    
  6. 自旋锁示例

     spinlock_t lock; 				//定义自旋锁
     spin_lock_init(&lock);			//初始化自旋锁
     /* 线程A */
     void ThreadA()
     {
         unsigned long flag;						//保存中断状态
         spin_lock_irqsave(&lock,flag);			//获取自旋锁,禁止中断
         /*  临界操作  */
         spin_unlock_irqrestore(&lock,flag);		//释放自旋锁,开启中断
     }
     /* 中断服务函数B */
     void IrqB()
     {
         spin_lock(&lock);		//获取自旋锁
         /*  临界操作  */
         spin_unlock(&lock);		//释放自旋锁
     }
    

四. 信号量

  1. 信号量特点

    • 信号量会使等待资源的线程进入休眠状态,因此适用于占用共享资源时间较久的场合。
    • 中断不能进入睡眠状态,故信号量不能在中断中使用。
    • 占用共享资源时间较短的场合,不适合使用信号量,因为频繁的线程切换会带来较大的系统开销
  2. 使用semaphore结构体表示信号量,该结构体定义在源码目录include/linux/semaphore.h

     struct semaphore {
         raw_spinlock_t		lock;
     	unsigned int 		count;
         struct list_head	wait_list;
     };
    
  3. 信号量API函数

     /* 初始化信号量 sem,设置信号量值为 val */
     void sema_init(struct semaphore *sem, int val);
     /* 获取信号量,因为会导致休眠,因此不能在中断中使用 */
     void down(struct semaphore *sem);
     /*获取信号量,和down类似,只是使用down进入休眠状态的线程不能被信号打断。而使用此函数进入休眠以后是可以被信号打断的(此信号是指外部中断信号,如ctrl+c)*/
     int down_interruptible(struct semaphore *sem);
     /* 尝试获取信号量,获取返回0。不能获取就返回非0,不会进入休眠 */
     int down_trylock(struct semaphore *sem);
     /* 释放信号量 */
     void up(struct semaphore *sem);
    
  4. 示例

     struct semaphore sem; 	//定义信号量
     sema_init(&sem,1);		//初始化二值信号量
     down(&sem);		//获取信号量
     /*   临界操作   */
     up(&sem);		//释放信号量
    

五. 互斥体

  1. Linux内核使用mutex结构体表示互斥体,该结构体定义在源码目录include/linux/mutex.h

     struct mutex {
     	/* 1: unlocked, 0: locked, negative: locked, possible waiters */ 
         atomic_t	count; 
         spinlock_t 	wait_lock;
     };
    
  2. 互斥体特点

    • 使用互斥体,一次只允许一个线程访问共享资源,不能递归申请互斥体
    • 互斥体会导致休眠,因此不能在中断中使用,中断中只能使用自旋锁
  3. 互斥体API函数

     void mutex_init(mutex *lock);			//初始化mutex
     /* 获取mutex,也就是给mutex上锁。如果获取不到就进休眠 */
     void mutex_lock(struct mutex *lock);
     /* 释放mutex,也就给mutex 解锁 */
     void mutex_unlock(struct mutex *lock);
     /* 尝试获取mutex,如果成功就返回1,如果失败就返回0 */
     int mutex_trylock(struct mutex *lock);
    
  4. 示例

     struct mutex mux_lock;			//互斥变量
     mutex_init(&mux_lock);			//初始化互斥体
     mutex_lock(&mux_lock);			//给mutex上锁
     /* 临界区操作 */
     mutex_unlock(&mux_lock);		//给mutex解锁
    

六. 扩展 — 死锁

  1. 产生死锁的原因:资源不足;资源分配不当;进程/线程运行顺序不当
  2. 产生死锁的必要条件:
    • 互斥条件:一个资源每次只能被一个进程/线程使用
    • 请求与保持条件:当一个进程/线程因某个事件阻塞时(如请求某个资源),对已有的资源不释放
    • 不可剥夺条件:当进程/线程未使用完某个资源时,不可剥夺
    • 循环等待条件:若干进程/线程首尾相接,形成循环等待资源的状态
  3. 死锁预防:破坏产生死锁的必要条件
    • 破坏请求与保持条件。静态分配资源法:当进程/线程执行前,申请所有需要的资源;动态分配资源法:进程/线程在申请资源时,本身不占有系统资源。
    • 破坏不可剥夺条件。进程/线程进入阻塞(如申请资源)时,释放已获得的资源到系统资源列表;下次执行时,需要获得先前的所有资源以及跳出阻塞(申请到资源)状态。
    • 破坏循环等待条件。根据资源使用的频繁程度,顺序进行编号;申请资源时必须按编号进行,只有获得较小编号资源才能申请较大编号的资源

以上是我在学习过程中的总结,不当之处请在评论区指出。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值