ios 线程同步

来源:互联网 发布:罗塞塔石碑mac破解版 编辑:程序博客网 时间:2024/05/16 08:56
线程同步
4-1 同步工具
1,原子操作
2,内存屏蔽和volatile变量
只确保每次操作都是从内存中获取信息,而不用寄存器内保存的数据
OSMemoryBarrier函数,设置内存屏蔽
volatile变量
3,锁

Table 4-1  Lock types

Lock

Description

Mutex

[互斥锁]

A mutually exclusive (or 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
[自旋锁]

A spin lock polls its lock condition repeatedly until that condition becomes true. Spin locks are most often used on multiprocessor systems where the expected wait time for a lock is small. In these situations, it is often more efficient to poll than to block the thread, which involves a context switch and the updating of thread data structures. The system does not provide any implementations of spin locks because of their polling nature, but you can easily implement them in specific situations. For information on implementing spin locks in the kernel, see 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  Mutex and atomic operation costs

Item

Approximate cost

Notes

Mutex acquisition time

Approximately 0.2 microseconds
[0.2微秒]

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
[0.05微秒]

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 anObject;

    [arrayLock lock];

    anObject = [myArray objectAtIndex:0];

    [arrayLock unlock];

    

     // 在处理doSomething的时候共享资源myArray可能被修改,下面的操作就是有风险的操作

    anObject doSomething];

===代码二---低效率的代码
       NSLock* arrayLock = [self GetArrayLock];

    NSMutableArray* myArray = GetSharedArray();

    

    id anObject;

    [arrayLock lock];

    anObject = [myArray objectAtIndex:0];


    // 在处理doSomething放到Lock里面,如果doSomething处理时间比较长,那么就形成了效率瓶颈,影响程序效率

    anObject doSomething];


    [arrayLock unlock];

===代码三---高效率的代码

    NSLock* arrayLock = [self GetArrayLock];

    NSMutableArray* myArray = GetSharedArray();

    

    id anObject;

    [arrayLock lock];

    anObject = [myArray objectAtIndex:0];

    // 把对象retain and save,防止在unlockmyArray里面的内容被修改

    [anObject retain];

    [arrayLock unlock];

    

    anObject doSomething];

    [anObject release];

4,当心死锁(DeadLock)和活锁(LiveLock)

任何线程试图同时获得多于一个锁,就有可能发生死锁的可能。当两个线程分别保持一个锁,A线程-A锁/B线程-B锁

避免死锁和活锁的最好方法是同一时间只拥有一个锁

5,正确使用volatile变量

关键字volatile只确保每次获取volatile变量时都是从内存加载变量,而不是使用寄存器里面的值,他不保证代码访问变量是正确的

4-5 使用原子操作
1,可用的原子运算和本地操作和相应的函数名

Table 4-3  Atomic math and logic operations

Operation

Function name

Description

Add

OSAtomicAdd32
OSAtomicAdd32Barrier
OSAtomicAdd64
OSAtomicAdd64Barrier

Adds two integer values together and stores the result in one of the specified variables.

Increment

OSAtomicIncrement32
OSAtomicIncrement32Barrier
OSAtomicIncrement64
OSAtomicIncrement64Barrier

Increments the specified integer value by 1.

Decrement

OSAtomicDecrement32
OSAtomicDecrement32Barrier
OSAtomicDecrement64
OSAtomicDecrement64Barrier

Decrements the specified integer value by 1.

Logical OR

OSAtomicOr32
OSAtomicOr32Barrier

Performs a logical OR between the specified 32-bit value and a 32-bit mask.

Logical AND

OSAtomicAnd32
OSAtomicAnd32Barrier

Performs a logical AND between the specified 32-bit value and a 32-bit mask.

Logical XOR

OSAtomicXor32
OSAtomicXor32Barrier

Performs a logical XOR between the specified 32-bit value and a 32-bit mask.

Compare and swap

OSAtomicCompareAndSwap32
OSAtomicCompareAndSwap32Barrier
OSAtomicCompareAndSwap64
OSAtomicCompareAndSwap64Barrier
OSAtomicCompareAndSwapPtr
OSAtomicCompareAndSwapPtrBarrier
OSAtomicCompareAndSwapInt
OSAtomicCompareAndSwapIntBarrier
OSAtomicCompareAndSwapLong
OSAtomicCompareAndSwapLongBarrier

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

OSAtomicTestAndSet
OSAtomicTestAndSetBarrier

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

OSAtomicTestAndClear
OSAtomicTestAndClearBarrier

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 MyMutexInitFunction()

{

    pthread_mutex_init(&mutexNULL);

}


void MyLockingFunction()

{

    // 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 allocinit];

    

    while(moreToDo)

    {

        /* Do another increment of calculation */

        

        /* until there's no more to do. */

        

        if([theLock tryLock])

        {

            /* Update the display used by all threads */

            [theLock 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需要配对出现

NSRecursiveLock *theLock = [[NSRecursiveLock allocinit];


- (void)MyRecursiveLockFunction:(int)value

{

    [theLock lock];

    

    if(value != 0)

    {

        -- value;

        [self MyRecursiveLockFunction:value];

    }

    

    [theLock unlock];

}

2)使用NSConditionLock对象---条件锁

条件锁是一个互斥锁,可以通过特定值来锁住和解锁。

NSConditionLock的锁住和解锁的方法,unlockWithCondition:和lock消息,或者lockWhenCondition:和unlock消息。两对可以任意组合使用

===生产者-消费者问题

#define NO_DATA     0

#define HAS_DATA    1


isEmpty = NO;

NSConditionLock  *condLock = [[NSConditionLock allocinitWithCondition: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

pthread_mutex_t mutex;

pthread_cond_t condition;


Boolean ready_to_go = true;


void MyCondInitFunction()

{

    pthread_mutex_init(&mutexNULL);

    pthread_cond_init(&conditionNULL);

}


void MyWaitOnConditionFunction()

{

    // 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);,再读取资源

            //用这个流程是比较清楚的   /*block-->unlock-->wait() return-->lock*/

    }

    

    // 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 signalThreadUsingCondition()

{

    // 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);

}


术语表

原创粉丝点击