常用的同步方式有: 互斥锁、条件变量、读写锁、记录锁(文件锁)和信号灯.
互斥锁:
顾名思义,锁是用来锁住某种东西的,锁住之后只有有钥匙的人才能对锁住的东西拥有控制权(把锁砸了,把东西偷走的小偷不在我们的讨论范围了)。所谓互斥, 从字面上理解就是互相排斥。因此互斥锁从字面上理解就是一点进程拥有了这个锁,它将排斥其它所有的进程访问被锁住的东西,其它的进程如果需要锁就只能等待,等待拥有锁的进程把锁打开后才能继续运行。 在实现中,锁并不是与某个具体的变量进行关联,它本身是一个独立的对象。进(线)程在有需要的时候获得此对象,用完不需要时就释放掉。
互斥锁的主要特点是互斥锁的释放必须由上锁的进(线)程释放,如果拥有锁的进(线)程不释放,那么其它的进(线)程永远也没有机会获得所需要的互斥锁。
互斥锁主要用于线程之间的同步。
条件变量:
上文中提到,对于互斥锁而言,如果拥有锁的进(线)程不释放锁,其它进(线)程永远没机会获得锁,也就永远没有机会继续执行后续的逻辑。在实际环境下,一 个线程A需要改变一个共享变量X的值,为了保证在修改的过程中X不会被其它的线程修改,线程A必须首先获得对X的锁。现在假如A已经获得锁了,由于业务逻 辑的需要,只有当X的值小于0时,线程A才能执行后续的逻辑,于是线程A必须把互斥锁释放掉,然后继续“忙等”。如下面的伪代码所示:
1.// get x lock
2.while(x
NO1互斥量(Mutex)
互斥量是实现最简单的锁类型,因此有一些教科书一般以互斥量为例对锁原语进行描述。互斥量的释放并不仅仅依赖于释放 *** 作,还可以引入一个定时器属性。如果在释放 *** 作执行前发生定时器超时,则互斥量也会释放代码块或共享存储区供其他线程访问。当有异常发生时,可使用try-finally语句来确保互斥量被释放。定时器状态或try-finally语句的使用可以避免产生死锁。
递归锁(Recursive
Lock)
递归锁是指可以被当前持有该锁的线程重复获取,而不会导致该线程产生死锁的锁类型。对递归锁而言,只有在当前持有线程的获取锁 *** 作都有一个释放 *** 作与之对应时,其他线程才可以获取该锁。因此,在使用递归锁时,必须要用足够的释放锁 *** 作来平衡获取锁 *** 作,实现这一目标的最佳方式是在单入口单出口代码块的两头一一对应地使用获取、释放 *** 作,做法和在普通锁中一样。递归锁在递归函数中最有用。但是,总的来说,递归锁比非递归锁速度要慢。需要注意的是:调用线程获得几次递归锁必须释放几次递归锁。
以下为一个递归锁的示例:
[cpp] view plain copy
Recursive_Lock L
void recursiveFunction (int count) {
L->acquire()
if (count >0) {
count = count - 1
recursiveFunction(count)
}
L->release()
}
读写锁(Read-Write
lock) 读写锁又称为共享独占锁(shared-exclusive
lock)、多读单写锁(multiple-read/single-write lock)或者非互斥信号量(non-mutual
exclusion
semaphore)。读写锁允许多个线程同时进行读访问,但是在某一时刻却最多只能由一个线程执行写 *** 作。对于多个线程需要同时读共享数据却并不一定进行写 *** 作的应用来说,读写锁是一种高效的同步机制。对于较长的共享数据,只为其设置一个读写锁会导致较长的访问时间,最好将其划分为多个小段并设置多个读写锁以进行同步。
这个读写锁我们在学习数据库的时候应该很熟悉的哟!
旋转锁(Spin
Lock)
旋转锁是一种非阻塞锁,由某个线程独占。采用旋转锁时,等待线程并不静态地阻塞在同步点,而是必须“旋转”,不断尝试直到最终获得该锁。旋转锁多用于多处理器系统中。这是因为,如果在单核处理器中采用旋转锁,当一个线程正在“旋转”时,将没有执行资源可供另一释放锁的线程使用。旋转锁适合于任何锁持有时间少于将一个线程阻塞和唤醒所需时间的场合。线程控制的变更,包括线程上下文的切换和线程数据结构的更新,可能比旋转锁需要更多的指令周期。旋转锁的持有时间应该限制在线程上下文切换时间的50%到100%之间(Kleiman,1996年)。在线程调用其他子系统时,线程不应持有旋转锁。对旋转锁的不当使用可能会导致线程饿死,因此需谨慎使用这种锁机制。旋转锁导致的饿死问题可使用排队技术来解决,即每个等待线程按照先进先出的顺序或者队列结构在一个独立的局部标识上进行旋转。
学习了这些,果然受益匪浅,在今后的coding中,我得挨个试试咯。
读写锁与互斥量类似,不过读写锁的并行性更高。读写锁可以有三种状态:(1)读模式加锁;(2)写模式加锁;(3)不加锁。
在写加锁状态时,在解锁之前,所有试图对这个锁加锁的线程都会被阻塞。在读加锁状态时,所有试图以读模式对它进行加锁的线程都可以得到访问权限。但是如果线程希望以写模式加锁,它必须阻塞,直至所有的线程释放读锁。
读写锁很适合于对数据结构读的次数远大于写的情况。
相关函数:
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr)
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock) // 成功则返回0,失败则返回错误代码
int pthread_rwlock_rdlock(pthread_rwlock_t *restrict rwlock) //读模式加锁
int pthread_rwlock_wrlock(pthread_rwlock_t *restrict rwlock)//写模式加锁
int pthread_rwlock_unlock(pthread_rwlock_t *restrick rwlock)
int pthread_rwlock_tryrdlock(pthread_rwlock_t *restrict rwlock)
int pthread_rwlock_trywrlock(pthread_rwlock_t *restrict rwlock)
int pthread_rwlock_trywrlock(pthread_rwlock_t *restrict rwlock)
相关示例:读者写者问题,这也是一个很经典的多线程题目,题目大意:有一个写者多个读者,多个读者可以同时读文件,但写者在写文件时不允许有读者在读取文件,同样有读者读文件时
#include <stdio.h>
#include <pthread.h>
#define Read_Num 2
pthread_rwlock_t lock
class Data
{
public:
Data(int i, float f): I(i),F(f)
{}
private:
int I
float F
}
Data *pdata = NULL
void *read(void * arg)
{
int id = (int)arg
while(true)
{
pthread_rwlock_rdlock(&lock)
printf(" reader %d is reading data!\n", id)
if(data == NULL)
{
printf("data is NULL\n")
}
else
{
printf("data: I = %d, F = %f \n", pdata->I, pdata->F)
}
pthread_rwlock_unlock(&lock)
}
pthread_exit(0)
}
void *write()
{
while(true)
{
pthread_rwlock_wrlock(&lock)
printf(" writer is writind data!\n")
if(pdata == NULL)
{
pdata = new Data(1, 1.1)
printf("Writer is writing data: %d, %f\n", pdata->I, pdata->F)
}
else
{
delete pdata
pdata = NULL
printf("writer free the data!")
}
pthread_rwlock_unlock(&lock)
}
pthread_exit(0)
}
void main()
{
pthread_t reader[Read_Num]
pthread_t writer
for(int i = 0i<Read_Numi++)
{
pthread_create(&read[i],NULL,read,(void *)i)
}
pthread_create(writer, NULL, write, NULL)
sleep(1)
return 0
}
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)