线程同步的几种方法
线程同步的方式包括:互斥锁、读写锁、条件变量、信号量和令牌。
-
互斥锁和读写锁: 提供对临界资源的保护,当多线程试图访问临界资源时,都必须通过获取锁的方式来访问临界资源。(临界资源:是被多线程共享的资源)当读写线程获取锁的频率 差别不大时,一般采用互斥锁,如果读线程访问临界资源的频率大于写线程,这个时候采用读写锁较为合适,读写锁允许多个读线程同时访问临界资源,读写线程必 须互斥访问临界资源。读写锁的实现采用了互斥锁,所以在读写次数差不多的情况下采用读写锁性能没有直接采用互斥锁来的高。
-
条件变量:提供线程之间的一种通知机制,当某一条件满足时,线程A可以通知阻塞在条件变量上的线程B,B所期望的条件已经满足,可以解除在条件变量上的阻塞操作,继续做其他事情。
-
信号量:提供对临界资源的安全分配。如果存在多份临界资源,在多个线程争抢临界资源的情况下,向线程提供安全分配临界资源的方法。如果临界资源的数量为1,将退化为锁。
-
令牌:一种高级的线程同步的方法。它既提供锁的安全访问临界资源的功能,又利用了条件变量使得线程争夺临界资源时是有序的。
下面提供Token在ACE中的一种实现方法。可以从下面的类图,可以看到ACE是如何设计Token的。
-
ACE_Token: 这个类是Token类,提供了获取和释放Token的方法。对于Token的获取策略,ACE提供两种实现:FIFO和LIFO。Token中对应着两个队列,一个是获取Token是为了写的队列,另外一个是获取Token是为了读的队列。
-
ACE_Token_Queue:队列是一个链表,该类提供对链表的管理操作。
-
ACE_Token_Entry:是队列中存放的元素,提供了对条件变量的封装,一个元素代表一个线程试图获取Token。如果Token已经被获取,线程需要阻塞在自己的Token上(队列Entry中的条件变量上)。等待Token持有者释放该Token,并通知阻塞的线程。
下面对ACE_Token中的组要方法提供分析。
-
获取
-
判断是否有线程已经持有互斥锁了,如果没人持有,表示临界资源是可用的,那么可以立即返回成功。
-
检查是不是线程尝试递归获取互斥锁。因为Token支持这种情况,所以也可立即返回成功。同时将nesting计数器自增。
-
创建一个Token Entry,并将其排入队列。
-
调用用户自定义的Hook方法,在线程进行sleep之前,用户可以调用自定义的Hook方法。
-
线程睡眠,如果线程唤醒后,发现当前线程不是Token的拥有者将继续睡眠。
-
线程被唤醒后,将Token entry从队列中删除。
-
释放
-
如果发现嵌套层数大于0,需要将嵌套层数的计数器减一,然后让该线程继续持有Token。
-
否则,该线程负责从队列中按照一定的策略,取出Token entry,并通过该Entry通知阻塞在Entry上的线程,资源被释放,你可以使用资源了。
注:Token Entry的创建者和删除者是同一个线程,并且在同一个方法中完成的(acquire)。