互斥锁与自旋锁

我们知道线程同步是并行编程中非常重要的手段,其中最典型的就是用pthreads提供的锁机制(lock)来对多个线程之间共享的临界区进行保护。我们知道pthreads也提供了多种锁的机制如:互斥锁、自旋锁、条件变量、读写锁。今天就先来讲讲自旋锁与互斥锁。
一、互斥锁
互斥锁也叫互斥量是我们实现同步的重要的工具,在线程访问共享资源之前对互斥量进行加锁,在线程访问共享资源后对互斥量进行解锁,通过加锁与解锁的的操作我们可以将原来多步的操作现在变成原子操作,系统只会在执行完锁内的代码之后才会切出去执行其他的线程。
在一个线程占用资源(钥匙)时,任何其他再试图申请同一份锁资源的线程都会被阻塞,直到当前线程释放了该锁资源。
在互斥锁被释放的时候,所有因为该锁被阻塞的线程都会变成可运行状态,第一个变为运行的线程就能对互斥量进行加锁(获取钥匙)其他线程就会看到锁资源依然被占用,只能继续等待。这样在一个时刻只能有一个线程在执行,多线程会按照一定的顺序在向前协同,所以互斥锁也被称为二元信号量

互斥锁的操作函数:
1、互斥锁的创建
创建锁资源就像创建变量一样,锁的类型是pthread_mutex_t。因为线程是共享数据区和堆区的,所以我们可以创建全局或静态的锁变量或者在堆上创建,这样就可以使得所有线程都可以看见锁,所以说实现线程间通信是非常简单的。
pthread_mutex_t mutex
2、互斥锁的初始化(pthread_mutex_init)

int pthread_mutex_init(pthread_mutex_t* restrict mutex,const pthread_mutexattr_t* restrict attr)

参数mutex为在外面定义的一个pthread_mutex_t数据类型的数据指针,init函数调用完成后,互斥锁的值会放在mutex指向的内存单元。要用默认的属性初始化互斥量,就将参数attr设为NULL 。
除了这种方式我们还可以定义一个互斥锁,用宏定义来给它赋值

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

这种方式相当于方式将上一种的attr设为NULL。也就是使用默认的属性初始化互斥量。
3、加锁操作
加锁:

int pthread_mutex_lock(pthread_mutex_t *mutex);

返回值:成功返回0,失败返回错误码。
一个线程可以调用pthread_mutex_lock获得锁,如果这时另一个线程先获得锁,则当前线程需要挂起等待,直到另一个线程调用pthread_mutex_unlock解锁,当前线程被唤醒,才能获得锁,并继续执行。可以看到,互斥锁就像轻量级的二元信号量一样,只能用在线程间,而信号量既可以用在线程间,也可以用在进程间。

int pthread_mutex_trylock(pthread_mutex_t *mutex)

返回值:成功返回0,失败返回错误码。
如果一个线程既想获得锁,又不想挂起等待,可以使用pthread_mutex_trylock,这个函数失败的话会返回EBUSY,而不会使线程挂起等待。注意这个函数调用一次,只会去申请一次锁资源
4、解锁操作

int pthread_mutex_unlock(pthread_mutex_t *mutex);

返回值:成功返回0,失败返回错误码
5、锁的销毁

int pthread_cond_destroy(pthread_cond_t *cond);

返回值:成功返回0,失败返回错误码。
二、自旋锁(底层实现,原子操作)
自旋锁与互斥锁是很类似的,只是自旋锁不会引起调用者的睡眠,如果自旋锁被别的执行单元保持那么调用者就会一直看是否该自旋锁的保持者已经释放了锁,“自旋”就是这么来的它的作用是为了解决某项资源的互斥使用。因为自旋锁不会引起调用者睡眠所以自旋锁的效率就远远高于互斥锁。
自旋锁是专为防止多处理器并发而引入的一种锁,它在内核中大量应用于中断处理等部分(对于单处理器来说,防止中断处理中的并发可简单采用关闭中断的方式,不需要自旋锁)。
自旋锁最多只能被一个内核任务持有,如果一个内核任务试图请求一个已被争用(已经被持有)的自旋锁,那么这个任务就会一直进行忙循环——旋转——等待锁重新可用。
不足之处:
1、自旋锁一直占用着cpu在未获得锁的情况下一直是运行着的(自旋),如果不能在很短时间内获得锁资源就会造成cpu的效率降低
2、用自旋锁可能会造成死锁,当其递归调用的时候就会造成死锁,调用某些其他函数也会造成死锁(copy_to_user()、copy_from_user()、kmalloc()等)

自旋锁的用法:
自旋锁适用于锁使用者保持锁时间比较短的情况下。
自旋锁的创建:spinlock_t x;
自旋锁的初始化:spin_lock_init(spinlock_t *x); (自旋锁在真正使用前必须先初始化)
内核中将定义和初始化合并为一个宏:DEFINE_SPINLOCK(x)

获得自旋锁:spin_lock(x); (只有在获得锁的情况下才返回,否则一直“自旋”spin_trylock(x),如立即获得锁则返回真,否则立即返回假)
释放锁:spin_unlock(x);
自旋锁的操作函数:

//加锁一个自旋锁函数
void spin_lock(spinlock_t *lock);                                   //获取指定的自旋锁
void spin_lock_irq(spinlock_t *lock);                               //禁止本地中断获取指定的锁
void spin_lock_irqsave(spinlock_t *lock, unsigned long flags);      //保存本地中断的状态,禁止本地中断,并获取指定的锁
void spin_lock_bh(spinlock_t *lock)                                 //安全地避免死锁, 而仍然允许硬件中断被服务


//释放一个自旋锁函数
void spin_unlock(spinlock_t *lock);                                 //释放指定的锁
void spin_unlock_irq(spinlock_t *lock);                             //释放指定的锁,并激活本地中断
void spin_unlock_irqrestore(spinlock_t *lock, unsigned long flags); //释放指定的锁,并让本地中断恢复到以前的状态
void spin_unlock_bh(spinlock_t *lock);                              //对应于spin_lock_bh


//非阻塞锁
int spin_trylock(spinlock_t *lock);                  //试图获得某个特定的自旋锁,如果该锁已经被争用,该方法会立刻返回一个非0值,
                                                     //而不会自旋等待锁被释放,如果成果获得了这个锁,那么就返回0.
int spin_trylock_bh(spinlock_t *lock);                           
//这些函数成功时返回非零( 获得了锁 ), 否则 0. 没有"try"版本来禁止中断.

//其他
int spin_is_locked(spinlock_t *lock);               //和try_lock()差不多

三、互斥锁与自旋锁的区别
信号量/互斥锁允许进程睡眠属于睡眠锁,自旋锁则不允许调用者睡眠,而是让其循环等待,所以有以下区别应用
1)、信号量和读写信号量适合于保持时间较长的情况,它们会导致调用者睡眠,因而自旋锁适合于保持时间非常短的情况
2)、自旋锁可以用于中断,不能用于进程上下文(会引起死锁)。而信号量不允许使用在中断中,而可以用于进程上下文
3)、自旋锁保持期间是抢占失效的,自旋锁被持有时,内核不能被抢占,而信号量和读写信号量保持期间是可以被抢占的

互斥锁的起始原始开销要高于自旋锁,但是基本是一劳永逸,临界区持锁时间的大小并不会对互斥锁的开销造成影响,而自旋锁是死循环检测,加锁全程消耗cpu,起始开销虽然低于互斥锁,但是随着持锁时间,加锁的开销是线性增长

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值