线程同步
4-1 同步工具
1,原子操作
2,内存屏蔽和volatile变量
只确保每次操作都是从内存中获取信息,而不用寄存器内保存的数据
OSMemoryBarrier函数,设置内存屏蔽
volatile变量
3,锁
Table 4-1
Lock | Description |
Mutex [互斥锁] | mutex) lock acts as a protective barrier around a resource. A mutex is a type of semaphore that grants access to only one thread at a time. If a mutex is in use and another thread tries to acquire it, that thread blocks until the mutex is released by its original holder. If multiple threads compete for the same mutex, only one at a time is allowed access to it. |
Recursive lock [递归锁] | A recursive lock is a variant on the mutex lock. A recursive lock allows a single thread to acquire the lock multiple times before releasing it. Other threads remain blocked until the owner of the lock releases the lock the same number of times it acquired it. Recursive locks are used during recursive iterations primarily but may also be used in cases where multiple methods each need to acquire the lock separately. |
Read-write lock | A read-write lock is also referred to as a shared-exclusive lock. This type of lock is typically used in larger-scale operations and can significantly improve performance if the protected data structure is read frequently and modified only occasionally. During normal operation, multiple readers can access the data structure simultaneously. When a thread wants to write to the structure, though, it blocks until all readers release the lock, at which point it acquires the lock and can update the structure. While a writing thread is waiting for the lock, new reader threads block until the writing thread is finished. The system supports read-write locks using POSIX threads only. For more information on how to use these locks, see the pthread man page. |
Distributed lock | A distributed lock provides mutually exclusive access at the process level. Unlike a true mutex, a distributed lock does not block a process or prevent it from running. It simply reports when the lock is busy and lets the process decide how to proceed. |
Spin lock | Kernel Programming Guide. |
Double-checked lock | A double-checked lock is an attempt to reduce the overhead of taking a lock by testing the locking criteria prior to taking the lock. Because double-checked locks are potentially unsafe, the system does not provide explicit support for them and their use is discouraged.[注意系统不显式支持该锁类型] |
注意: 大部分锁类型都合并了内存屏障来确保在进入临界区之前它前面的加载和存储指令都已经完成。
4,条件
信号量的另一种形式,它允许在条件真的时候线程间相互发送信号(signal,这个也是和Lock锁区别之一)。条件和互斥锁的区别在于多个线程被允许同时访问一个条件。但是对于同一时间,互斥锁只能被一个线程访问。
5,执行Selector例程
参照Cocoa执行Selector源
4-2 同步的成本和性能
Table 4-2
Item | Approximate cost | Notes |
Mutex acquisition time | Approximately 0.2 microseconds | This is the lock acquisition time in an uncontested case. If the lock is held by another thread, the acquisition time can be much greater. The figures were determined by analyzing the mean and median values generated during mutex acquisition on an Intel-based iMac with a 2 GHz Core Duo processor and 1 GB of RAM running Mac OS X v10.5. |
Atomic compare-and-swap | Approximately 0.05 microseconds | This is the compare-and-swap time in an uncontested case. The figures were determined by analyzing the mean and median values for the operation and were generated on an Intel-based iMac with a 2 GHz Core Duo processor and 1 GB of RAM running Mac OS X v10.5. |
4-3 线程安全和信号量
4-4 线程安全设计技巧
1,完全避免同步
2,了解同步的限制
对于所有线程操作同一资源时,必须使用同一互斥锁(同一资源-同一互斥锁)
3,注意对代码正确性的威胁
===代码一---有风险的代码
NSLock* arrayLock = [self GetArrayLock];
NSMutableArray* myArray = GetSharedArray();
id
lock];
objectAtIndex:0];
unlock];
// 在处理doSomething的时候共享资源myArray可能被修改,下面的操作就是有风险的操作
[anObject doSomething];
===代码二---低效率的代码
NSLock
* arrayLock = [
self
GetArrayLock
];
NSMutableArray* myArray = GetSharedArray();
id
lock];
objectAtIndex:0];
// 在处理doSomething放到Lock里面,如果doSomething处理时间比较长,那么就形成了效率瓶颈,影响程序效率
[anObject doSomething];
unlock];
===代码三---高效率的代码
NSLock* arrayLock = [self GetArrayLock];
NSMutableArray* myArray = GetSharedArray();
id
lock];
objectAtIndex:0];
// 把对象retain and save,防止在unlock,myArray里面的内容被修改
retain];
unlock];
[anObject doSomething];
release];
4,当心死锁(DeadLock)和活锁(LiveLock)
任何线程试图同时获得多于一个锁,就有可能发生死锁的可能。当两个线程分别保持一个锁,A线程-A锁/B线程-B锁
避免死锁和活锁的最好方法是同一时间只拥有一个锁
5,正确使用volatile变量
关键字volatile只确保每次获取volatile变量时都是从内存加载变量,而不是使用寄存器里面的值,他不保证代码访问变量是正确的
4-5 使用原子操作
1,可用的原子运算和本地操作和相应的函数名
Table 4-3
Operation | Function name | Description |
Add | OSAtomicAdd32OSAtomicAdd32BarrierOSAtomicAdd64OSAtomicAdd64Barrier | Adds two integer values together and stores the result in one of the specified variables. |
Increment | OSAtomicIncrement32OSAtomicIncrement32BarrierOSAtomicIncrement64OSAtomicIncrement64Barrier | Increments the specified integer value by 1. |
Decrement | OSAtomicDecrement32OSAtomicDecrement32BarrierOSAtomicDecrement64OSAtomicDecrement64Barrier | Decrements the specified integer value by 1. |
Logical OR | Performs a logical OR between the specified 32-bit value and a 32-bit mask. | |
Logical AND | Performs a logical AND between the specified 32-bit value and a 32-bit mask. | |
Logical XOR | Performs a logical XOR between the specified 32-bit value and a 32-bit mask. | |
Compare and swap | OSAtomicCompareAndSwap32OSAtomicCompareAndSwap32BarrierOSAtomicCompareAndSwap64OSAtomicCompareAndSwap64BarrierOSAtomicCompareAndSwapPtrOSAtomicCompareAndSwapPtrBarrierOSAtomicCompareAndSwapIntOSAtomicCompareAndSwapIntBarrierOSAtomicCompareAndSwapLongOSAtomicCompareAndSwapLongBarrier | Compares a variable against the specified old value. If the two values are equal, this function assigns the specified new value to the variable; otherwise, it does nothing. The comparison and assignment are done as one atomic operation and the function returns a Boolean value indicating whether the swap actually occurred. |
Test and set | Tests a bit in the specified variable, sets that bit to 1, and returns the value of the old bit as a Boolean value. Bits are tested according to the formula (0×80 >> (n & 7)) of byte((char*)address + (n >> 3)) where n is the bit number and address is a pointer to the variable. This formula effectively breaks up the variable into 8-bit sized chunks and orders the bits in each chunk in reverse. For example, to test the lowest-order bit (bit 0) of a 32-bit integer, you would actually specify 7 for the bit number; similarly, to test the highest order bit (bit 32), you would specify 24 for the bit number. | |
Test and clear | Tests a bit in the specified variable, sets that bit to 0, and returns the value of the old bit as a Boolean value. Bits are tested according to the formula (0×80 >> (n & 7)) of byte((char*)address + (n >> 3)) where n is the bit number and address is a pointer to the variable. This formula effectively breaks up the variable into 8-bit sized chunks and orders the bits in each chunk in reverse. For example, to test the lowest-order bit (bit 0) of a 32-bit integer, you would actually specify 7 for the bit number; similarly, to test the highest order bit (bit 32), you would specify 24 for the bit number. |
4-6 使用锁
1,使用POSIX锁
pthread_mutex_t是POSIX互斥锁的数据结构,pthread_mutex_lock/pthread_mutex_unlock函数用来加锁和解锁
// Listing 4-2 Using a mutex lock
void
{
pthread_mutex_init(&mutex, NULL);
}
void
{
// Lock the mutex
pthread_mutex_lock(&mutex);
// Do real the work
// unlock the mutex
pthread_mutex_unlock(&mutex);
}
2,使用NSLock类
Cocoa中所有的锁的接口实际上都是通过NSLocking协议定义的,除了定义了lock/unlock方法外,还定义了tryLock/lockBeforeDate:方法。tryLock方法试图获取一个锁,如果所不可用,他不会阻塞线程,相反,他只返回NO。lockBeforeDate:方法试图获取一个锁,如果锁没有在规定的时间内被获取到,他会让线程,从阻塞状态变为非阻塞状态(或者返回NO)。
void)testUsingLock
{
BOOL moreToDo = NO;
NSLock *theLock = [[NSLock alloc] init];
while(moreToDo)
{
/* Do another increment of calculation */
/* until there's no more to do. */
if([theLock tryLock])
{
/* Update the display used by all threads */
unlock];
}
}
}
3,使用@synchronized指令
@synchronized指令做和其他互斥锁一样的工作(防止不同线程在同一时间获取同一个锁)
void)MyMethod:(id)anObj
{
@synchronized(anObj)
{
// Everything between the braces is protected by the @synchronized directive.
}
}
@synchronized(anObj)指令的对象是一个用来区别保护块的唯一标示符,如果在不同线程调用上述方法,如果每次在线程传递的不同的对象给anObj,那么每次他都将拥有他的锁,并持续处理,而不被其他线程阻塞。如果传递的是同一参数,那么该线程被阻塞,直到前一个线程解锁后,才能继续处理
另,@synchronized()块隐式的添加一个异常处理例程来保护代码,所以需要在程序中启用异常处理。
4,使用其他锁
1)使用NSRecursiveLock对象---递归锁
对同一线程,可以多次获得(lock)而不会造成死锁,注意的是lock/unlock需要配对出现
*theLock = [[NSRecursiveLock alloc] init];
void)MyRecursiveLockFunction:(int)value
{
theLock lock];
if(value != 0)
{
-- value;
[self MyRecursiveLockFunction:value];
}
theLock unlock];
}
2)使用NSConditionLock对象---条件锁
条件锁是一个互斥锁,可以通过特定值来锁住和解锁。
NSConditionLock的锁住和解锁的方法,unlockWithCondition:和lock消息,或者lockWhenCondition:和unlock消息。两对可以任意组合使用
===生产者-消费者问题
0
1
= NO;
NSConditionLock *condLock = [[NSConditionLock alloc] initWithCondition:NO_DATA];
// 生产者线程数据处理流程
void)MyConditionLockPruductor
{
while(true)
{
condLock lock];
/* Add data to the queue */
[condLock unlockWithCondition:HAS_DATA];
}
}
// 消费者线程数据处理流程
void)MyConditionLockCustomer
{
while(true)
{
[condLock lockWhenCondition:HAS_DATA];
/* remove data from the queue */
[condLock unlockWithCondition:(isEmpty?NO_DATA:HAS_DATA)];
// Process the data locally.
}
}
3)使用NSDistributedLock对象---分布锁
用于协调多台主机访问某系共享资源,比如一个文件/一个目录等。和其他lock锁的不同,NSDistributedLock对象并没有实现NSLocking协议,所以他没有lock方法。不过提供了一个tryLock方法,可以通过breadLock方法打破现在锁。
4-7 使用条件
1,使用NSCondition类
把锁和条件数据结构封装在一个单一对象里面,造成使用NSCondition对象就像使用NSLock,但是多了一个Wait方法。
// Listing 4-3 Using a Cocoa condition
void)testUsingConditoin
{
[cocaoCondition lock];
while(timeToDoWork <= 0)
{
[cocaoCondition wait];
}
-- timeToDoWork;
// Do real work here
[cocaoCondition unlock];
}
// Listing 4-4 Signaling a Cocoa condition
void)signalCocaoConditon
{
[cocaoCondition lock];
++ timeToDoWork;
[cocaoCondition signal];
[cocaoCondition unlock];
}
2,使用POSIX条件
条件变量是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:一个线程等待"条件变量的条件成立"而挂起;另一个线程使"条件成立"(给出条件成立信号)。为了防止竞争,条件变量的使用总是和一个互斥锁结合在一起。
1)创建和注销
条件变量和互斥锁一样,都有静态动态两种创建方式,静态方式使用PTHREAD_COND_INITIALIZER常量,如下:
pthread_cond_t cond=PTHREAD_COND_INITIALIZER
动态方式调用pthread_cond_init()函数,API定义如下:
int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr)
尽管POSIX标准中为条件变量定义了属性,但在LinuxThreads中没有实现,因此cond_attr值通常为NULL,且被忽略。
注销一个条件变量需要调用pthread_cond_destroy(),只有在没有线程在该条件变量上等待的时候才能注销这个条件变量,否则返回EBUSY。因为Linux实现的条件变量没有分配什么资源,所以注销动作只包括检查是否有等待线程。API定义如下:
int pthread_cond_destroy(pthread_cond_t *cond)
2)等待和激发
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex)
int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime)
等待条件有两种方式:无条件等待pthread_cond_wait()和计时等待pthread_cond_timedwait(),其中计时等待方式如果在给定时刻前条件没有满足,则返回ETIMEOUT,结束等待,其中abstime以与time()系统调用相同意义的绝对时间形式出现,0表示格林尼治时间1970年1月1日0时0分0秒。
无论哪种等待方式,都必须和一个互斥锁配合,以防止多个线程同时请求pthread_cond_wait()(或pthread_cond_timedwait(),下同)的竞争条件(Race Condition)。mutex互斥锁必须是普通锁(PTHREAD_MUTEX_TIMED_NP)或者适应锁(PTHREAD_MUTEX_ADAPTIVE_NP),且
在调用pthread_cond_wait()前必须由本线程加锁(pthread_mutex_lock()),而在更新条件等待队列以前,mutex保持锁定状态,并在线程挂起进入等待前解锁。在条件满足从而离开pthread_cond_wait()之前,mutex将被重新加锁,以与进入pthread_cond_wait()前的加锁动作对应
。
激发条件有两种形式,pthread_cond_signal()激活一个等待该条件的线程,存在多个等待线程时按入队顺序激活其中一个;而pthread_cond_broadcast()则激活所有等待线程。
===使用POSIX条件
// Listing 4-5 Using a POSIX condition
mutex;
condition;
Boolean ready_to_go = true;
void
{
pthread_mutex_init(&mutex, NULL);
pthread_cond_init(&condition, NULL);
}
void
{
// Lock the mutex
pthread_mutex_lock(&mutex);
// if the predicate is already set, then the while loop is bypassed;
// otherwise, the thread sleeps until the predicate is set
while(ready_to_go == false)
{//这个while要特别说明一下,单个pthread_cond_wait功能很完善,为何这里要有一个while (head == NULL)呢?因为pthread_cond_wait里的线程可能会被意外唤醒,如果这个时候head != NULL,则不是我们想要的情况。这个时候,应该让线程继续进入pthread_cond_wait
pthread_cond_wait(&condition, &mutex);// pthread_cond_wait会先解除之前的pthread_mutex_lock锁定的mutex,然后阻塞在等待对列里休眠,直到再次被唤醒(大多数情况下是等待的条件成立而被唤醒,唤醒后,该进程会先锁定先pthread_mutex_lock(&mutex);,再读取资源
//用这个流程是比较清楚的
}
// Do work. (The mutex should stay locked.)
// Reset the predicate and release the mutex.
ready_to_go = false;
pthread_mutex_unlock(&mutex);
}
===使用POSIX条件,通知解锁
// Listing 4-6 Signaling a condition lock
void
{
// At the point, there should be work for the other thread to do
pthread_mutex_lock(&mutex);
ready_to_go = true;
// signal the other thread to begin work
pthread_cond_signal(&condition);
pthread_mutex_unlock(&mutex);
}
术语表
- 应用(application) 一个显示一个图形用户界面给用户的特定样式程序。
- 条件(condition) 一个用来同步资源访问的结构。线程等待某一条件来决定是否被允许继续运行,直到其他线程显式的给该条件发送信号。
- 临界区(critical section) 同一时间只能不被一个线程执行的代码。
- 输入源(input source) 一个线程的异步事件源。输入源可以是基于端口的或手工触发,并且必须被附加到某一个线程的 run loop 上面。
- 可连接的线程(join thread) 退出时资源不会被立即回收的线程。可连接的线程在资源被回收之前必须被显式脱离或由其他线程连接。可连接线程