并发和竞态

并发和竞态

1.       并发及其管理

竞态通常作为对资源的共享访问结果而产生。

当两个执行线程需要访问相同的数据结构(或硬件资源)时,混合的可能性就永远存在。因此在设计自己的驱动程序时,第一个要记住的原则是,只要可能,就应该避免资源的共享。如果没有并发的访问,也就不会有竞态的产生。因此,仔细编写的内核代码应该具有最少的共享。这种思想的最明显应用就是避免使用全局变量。

2.       信号量和互斥体

scull添加锁定。目的是使对scull数据结构的操作是原子的,这意味着在涉及到其他执行线程之前,整个操作就已经结束了。对我们的内存泄漏示例来说,需要确保当一个线程发现特定内存块需要分配时,它应该拥有执行分配的机会,并需要在其他线程执行同一测试之前完成这个工作。为此,我们必须建立临界区:在任意给定的时刻,代码只能被一个线程执行。

信号量:一个信号量本质上是一个整数值,它和一对函数(P和V)联合使用.希望进入临界区的进程将在相关信号量上调用P;如果信号量的值大于零,则该值会减小1,而进程可以继续。相反,如果信号量为0(或更小),进程必须等待直到其他人释放该信号量。对信号量的解锁通过调用V完成;该函数增加信号量的值,并在必要时唤醒等待的进程。

当信号量用于互斥时(即避免多个进程同时在一个临界区中运行),信号量的值应初始化1.这种信号量在任何给定时刻只能由单个进程或线程拥有。在这种使用模式下,一个信号量有时也称为一个“互斥体(mutex)”,它是互斥(mutual  exclusion)的简称。Linux内核中几乎所有的信号量均用于互斥。

3.       Linux信号量的实现

定义信号量:struct semaphore  sem;

用于声明和初始化用在互斥模式中的信号量的两个宏:

DECLARE_MUTEX(sem);//sem被初始化为1

DECLARE_MUTEX_LOCKED(sem);//sem被初始化为0

在运行时初始化信号量:

Voidinit_MUTEX(struct  semaphore  *sem);

Voidinit_MUTEX_LOCKED(struct  semaphore *sem );

在Linux世界中,P函数被称为down——down指的是该函数减少了信号量的值,它也许将调用者置于休眠状态,然后等待信号量变得可用,之后授予调用者对被保护资源的访问。

Voiddown(struct  semaphore *sem);//不可中断,,不建议使用

Int  down_interruptible(struct  semaphore *sem);//可被信号中断,常用

Int  down_trylock(struct  semaphore *sem);//不会休眠,在信号量不可用时立即返回

当一个线程成功调用上述down的某个版本后,就称为该线程拥有了该信号量。这样,该线程就赋予访问由该信号量保护的临界区的权利。当互斥操作完成后,必须返回该信号量。Linux等价于V的函数是up:

Voidup(struct  semaphore *sem);调用up之后,调用者不再拥有该信号量。

4.       自旋锁

信号量对互斥来讲是非常有用的工具,但他不是内核提供的唯一的这类工具,相反,大多数锁定通过称为“自旋锁(spinlock)”的机制实现。和信号量不同,自旋锁可在不能休眠的代码中使用,比如中断处理历程。在正确使用的情况下,自旋锁通常可以提供比信号量更高的性能。

自旋锁是一个互斥设备,它只能有两个值:“锁定”和“解锁”。它通常实现为某个整数值中的某个位。希望获得某特定锁的代码测试相关的位。如果锁可用,则“锁定”位被设置,而代码继续进入临界区;相反,如果被其他人获得,则代码进入忙循环并重复检查这个锁,直到该锁可用为止。这个循环就是自旋锁的“自旋”部分。当存在自旋锁时,等待执行忙循环的处理器做不了任何有用的工作。

自旋锁和信号量(睡眠锁)的区别:获得不到信号量时会将CPU让出来,但是自旋锁会一直占用CPU.

信号量可能允许有多个持有者,而自旋锁在任何时候只允许一个持有者

信号量适合于保持时间长的情况,而自旋锁适合于保持时间非常短的情况。

自旋锁会导致死循环,锁定期间不允许阻塞,因此要求锁定的临界区小;信号量允许临界区阻塞,可以适用于临界区大的情况。

5.       自旋锁API介绍

1)        自旋锁初始化

spinlock_t  my_lock=SPIN_LOCK_UNLOCKED;//在编译时期

void  spin_lock_init(spinlock_t  *lock);//或者在运行时调用

2)        在进入临界区之前,必须调用下面函数获得需要的锁

Void spin_lock(spinlock_t  *lock);//所有的自旋锁等待在本质上都是不可中断的,一旦调用了spin_lock,在获得锁之前一直处于自旋状态。

3)        释放已经获得的锁

Void  spin_unlock(spinlock_t  *lock);

6.       自旋锁和原子上下文

适用于自旋锁的核心规则是:任何拥有自旋锁的代码都必须是原子的。它不能休眠,事实上,它不能因为任何原因放弃处理器,除了中断服务以外(某些情况下此时也不能放弃处理器)。任何时候,只要内核代码拥有自旋锁,在相关处理器上的抢占就会被禁止。

在拥有自旋锁时禁止中断(仅在本地CPU上)。

自旋锁使用上的最后一个重要原则是,自旋锁必须在可能的最短时间内拥有。拥有自旋锁的时间越长,其他处理器不得不自旋以等待释放该自旋锁的时间就越长,而它不得不永远自旋的可能性就越大。长的锁拥有时间将阻止对当前处理器的调度,这意味着更高优先级饿进程(真正应该获得CPU的进程)不得不等待。

7.       规则

如果某个获得锁的函数要调用其他同样试图获得这个锁的函数,我们的代码就会死锁。不论是信号量还是自旋锁,都不允许拥有者第二次获得这个锁;如果试图这么做,系统将挂起。

为了让锁定正确工作,则不得不编写一些函数,这些函数假定调用者已经获得了相关的锁。通常,内部的静态函数可通过这种方式编写,而提供给外部调用的函数则必须显式地处理锁定。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
条件(race condition)和数据争(data race)是并发编程中常见的问题,它们可能会导致程序的不确定行为和错误结果。 条件指的是多个并发操作在没有适当同步的情况下,对共享资源进行读写操作,从而导致结果的不确定性。具体来说,条件发生在满足以下条件时: 1. 多个协程同时访问同一个共享资源。 2. 至少有一个协程对该共享资源进行写操作。 3. 对共享资源的访问没有适当的同步机制进行保护。 数据争是条件的一种特殊情况,它指的是多个协程同时对同一个共享变量进行读写操作,且至少有一个协程进行写操作。数据争会导致未定义的行为,因为读写操作之间的顺序是不确定的,可能会产生意外结果。 数据争可能导致以下问题: 1. 脏读(Dirty read):一个协程在另一个协程修改共享变量之前读取该变量的值,从而读取到了未完成的写操作的结果。 2. 条件:多个协程通过争访问共享资源,导致结果的不确定性。 3. 无效的计算结果:由于数据争导致共享变量的值在不同协程之间不一致,可能会导致计算结果的错误。 为了避免条件和数据争,需要使用合适的同步机制来保护共享资源,例如使用互斥锁、读写锁、通道等。同时,编写并发安全的代码也需要考虑避免共享资源的过度使用,尽量减少共享数据的需求。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值