Chinaunix首页 | 论坛 | 博客
  • 博客访问: 626280
  • 博文数量: 133
  • 博客积分: 1566
  • 博客等级: 上尉
  • 技术积分: 1230
  • 用 户 组: 普通用户
  • 注册时间: 2010-12-01 09:31
文章分类

全部博文(133)

文章存档

2019年(1)

2018年(1)

2017年(8)

2016年(9)

2015年(17)

2014年(4)

2013年(31)

2012年(25)

2011年(36)

2010年(1)

我的朋友

分类: 系统运维

2012-11-19 16:03:18

线程同步
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(&mutex, NULL);

}


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 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 */

            [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 alloc] init];


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

pthread_mutex_t mutex;

pthread_cond_t condition;


Boolean ready_to_go = true;


void MyCondInitFunction()

{

    pthread_mutex_init(&mutex, NULL);

    pthread_cond_init(&condition, NULL);

}


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

}


术语表

  •   应用(application) 一个显示一个图形用户界面给用户的特定样式程序。

  •   条件(condition) 一个用来同步资源访问的结构。线程等待某一条件来决定是否被允许继续运行,直到其他线程显式的给该条件发送信号。

  •   临界区(critical section) 同一时间只能不被一个线程执行的代码。

  •   输入源(input source) 一个线程的异步事件源。输入源可以是基于端口的或手工触发,并且必须被附加到某一个线程的 run loop 上面。

  •   可连接的线程(join thread) 退出时资源不会被立即回收的线程。可连接的线程在资源被回收之前必须被显式脱离或由其他线程连接。可连接线程􏰀供了一个返回值给连接它的线程。

  •   主线程(main thread) 当创建进程时一起创建的特定类型的线程。当程序的主线程退出,则程序即退出。

  •   互斥锁(mutex) 􏰀供共享资源互斥访问的锁。一个互斥锁同一时间只能被一个线程拥有。试图获取一个已经被其他线程拥有的互斥锁,会把当前线程置于休眠状态知道该锁被其他线程释放并让当前线程获得。

  •   操作对象(operation object)
    NSOperation
    类的实例。操作对象封装了和某一任务相关的代码和数据到一个执行单元里面。

  •   操作队列(operation queue)
    NSOperationQueue
    类的实例。操作队列管理操作对象的执行。

  •   进程(process) 应用或程序的运行时实例。一个进程拥有独立于分配给其他程序的的内存空间和系统资源(包括端口权限)。进程总是包含至少一个线程(即主线程)和

任意数量的额外线程。

  •   程序(program) 可以用来执行某些任务的代码和资源的组合。程序不需要一个图形用户界面, 尽管图形应用也被称为程序。

  •   递归锁(recursive lock) 可以被同一线程多次锁住的锁。

  •   Run loop(运行循环) 一个事件处理循环,在此期间事件被接收并分配给合适的处理例程。

  •   Run loop 模式(run loop mode)
    与某一特定名称相关的输入源、定时源和 run loop 观察者的集合。当运行在某一特定“模式”下,一个 run loop 监视和该模式相关的源和观察者。

  •   Run loop 对象(run loop object)
    NSRunLoop
    类或 CFRunLoopRef 不透明类型的实例。这些对象􏰀供线程里面实现事件处理循环的接口。

  •   Run loop 观察者(run loop observer)
    run loop 运行的不同阶段时接收通知的对象。

  •   信号量(semaphore) 一个受保护的变量,它限制共享资源的访问。互斥锁(mutexes)和条件 (conditions)都是不同类型的信号量。

  •   任务(task)
    要执行的工作数量。尽管一些技术(最显著的是
    Carbon 多进程服务—Carbon Multiprocessing Services)使用该术语的意义有时不同,但是最通用的用法 是表明需要执行的工作数量的抽象概念。

  •   线程(thread) 进程里面的一个执行过程流。每个线程都有它自己的栈空间,但除此之外同一进程的其他线程共享内存。

  •   定时源(timer source) 为线程同步事件的源。定时器产生预定时间将要执行的一次或重复事件。 

阅读(25713) | 评论(0) | 转发(2) |
给主人留下些什么吧!~~