iOS如何保证线程的安全,iOS多线程安全

作者:澳门新葡京平台游戏

自旋锁和互斥锁

  • 相同点:
    • 都能保证同一时间只有一个线程访问共享资源。都能保证线程安全。
  • 不同点:
    • 互斥锁:如果共享数据已经有其他线程加锁了,线程会进入休眠状态等待锁。一旦被访问的资源被解锁,则等待资源的线程会被唤醒。
    • 自旋锁:如果共享数据已经有其他线程加锁了,线程会以死循环的方式等待锁,一旦被访问的资源被解锁,则等待资源的线程会立即执行。
  • 自旋锁的效率高于互斥锁。

YY大神推荐使用信号量dispatch_semaphore作为自旋锁的替代方案。

dispatch_semaphore_t signal = dispatch_semaphore_create;dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, 5.0f * NSEC_PER_SEC); //线程1dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSLog(@"线程1 holding"); dispatch_semaphore_wait(signal, timeout); //signal 值 -1 NSLog(@"线程1 sleep"); sleep; NSLog; dispatch_semaphore_signal; //signal 值  1 NSLog(@"线程1 post singal");}); //线程2dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSLog(@"线程2 holding"); dispatch_semaphore_wait(signal, timeout); NSLog(@"线程2 sleep"); sleep; NSLog; dispatch_semaphore_signal; NSLog(@"线程2 post signal");});

dispatch_semaphore_create为创建信号,()中数字表示可以同时几个线程使用信号。为1表示同步使用。上述代码如果此处标2就和没设置信号量一样,并发自行运行。如果设置为0,则一律等待overTime时自动释放,所有代码都不执行,理论上也具有同步作用。dispatch_semaphore_wait中传入的timeout表示最长加锁时间,自动释放锁后,其它线程可以获取信号并继续运行。

pthread表示的是POSIX thread,定义的是一组跨平台线程相关的API。pthread_mutex互斥锁是一个非递归锁,如果同一线程重复调用加锁会造成死锁。用法比较简单

static pthread_mutex_t pmutexLock;pthread_mutex_init(&pLock, NULL); //1.线程2dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ NSLog(@"线程2 befor lock"); pthread_mutex_lock(&pLock); NSLog; pthread_mutex_unlock(&pLock);}); //2.线程1dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSLog(@"线程1 before lock"); pthread_mutex_lock(&pLock); sleep; NSLog; pthread_mutex_unlock(&pLock);});

pthread_mutex(recursive) 递归锁,比较安全,同一线程有且仅有一次加锁,重复加锁不会死锁。无论加锁几次,只需解锁一次。

static pthread_mutex_t pLock;pthread_mutexattr_t attr;pthread_mutexattr_init(&attr); //初始化attr赋初值pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); //设置锁类型为递归锁pthread_mutex_init(&pLock, &attr);pthread_mutexattr_destroy(&attr); //1.线程1dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ static void (^RecursiveBlock); RecursiveBlock = ^(int value) { pthread_mutex_lock(&pLock); if (value > 0) { NSLog(@"value: %d", value); RecursiveBlock(value - 1); } }; NSLog(@"线程1 before lock"); RecursiveBlock; NSLog; pthread_mutex_unlock(&pLock); NSLog(@"线程1 unlock");}); //2.线程2dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSLog(@"线程2 before lock"); pthread_mutex_lock(&pLock); NSLog; pthread_mutex_unlock(&pLock); NSLog(@"线程2 unlock");});

@synchronized 实际上是把修饰对象当做锁来使用。这是通过一个哈希表来实现的,OC 在底层使用了一个互斥锁的数组,通过对对象去哈希值来得到对应的互斥锁。

图片 1image

相同类型的锁递归锁和普通锁效率相差接近一倍,如果不会在循环或者递归中频繁使用加锁和解锁,不建议使用递归锁;从效率上讲,建议用互斥锁pthread_mutex或者信号量dispatch_semaphore作为替代。OSSpinlock各路大神都说有问题,还有iOS的各种锁就不一一细说了,如果想了解更多的线程锁自行去搜索哈。

6.NSConditionLock条件锁

基于NSLock

[lock lockWhenCondition:HAS_DATA];

[lock unlockWithCondition:NO_DATA];

只有满足一定条件的钥匙才能打开这个锁,也只有满足一定条件的锁才能锁上。

NSConditionLock (条件锁)

自旋锁和互斥锁

自旋锁(Spin lock)

自旋锁与互斥锁有点类似,只是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是 否该自旋锁的保持者已经释放了锁,"自旋"一词就是因此而得名。其作用是为了解决某项资源的互斥使用。因为自旋锁不会引起调用者睡眠,所以自旋锁的效率远 高于互斥锁。虽然它的效率比互斥锁高,但是它也有些不足之处:1、自旋锁一直占用CPU,他在未获得锁的情况下,一直运行--自旋,所以占用着CPU,如果不能在很短的时 间内获得锁,这无疑会使CPU效率降低。2、在用自旋锁时有可能造成死锁,当递归调用时有可能造成死锁,调用有些其他函数也可能造成死锁,如 copy_to_user()、copy_from_user()、kmalloc()等。因此我们要慎重使用自旋锁,自旋锁只有在内核可抢占式或SMP的情况下才真正需要,在单CPU且不可抢占式的内核下,自旋锁的操作为空操作。自旋锁适用于锁使用者保持锁时间比较短的情况下。

互斥锁

互斥锁属于sleep-waiting类型的锁。例如在一个双核的机器上有两个线程,它们分别运行在Core0和 Core1上。假设线程A想要通过pthread_mutex_lock操作去得到一个临界区的锁,而此时这个锁正被线程B所持有,那么线程A就会被阻塞 ,Core0 会在此时进行上下文切换(Context Switch)将线程A置于等待队列中,此时Core0就可以运行其他的任务而不必进行忙等待。而自旋锁则不然,它属于busy-waiting类型的锁,如果线程A是使用pthread_spin_lock操作去请求锁,那么线程A就会一直在 Core0上进行忙等待并不停的进行锁请求,直到得到这个锁为止。

两种锁的加锁原理

互斥锁:线程会从sleep——>running,过程中有上下文的切换,cpu的抢占,信号的发送等开销。

自旋锁:线程一直是running(加锁——>解锁),死循环检测锁的标志位,机制不复杂。

对比互斥锁的起始原始开销要高于自旋锁,但是基本是一劳永逸,临界区持锁时间的大小并不会对互斥锁的开销造成影响,而自旋锁是死循环检测,加锁全程消耗cpu,起始开销虽然低于互斥锁,但是随着持锁时间,加锁的开销是线性增长。

两种锁的应用

互斥锁用于临界区持锁时间比较长的操作,比如下面这些情况都可以考虑

  • 1 临界区有IO操作
  • 2 临界区代码复杂或者循环量大
  • 3 临界区竞争非常激烈
  • 4 单核处理器

至于自旋锁就主要用在临界区持锁时间非常短且CPU资源不紧张的情况下,自旋锁一般用于多核的服务器。

原子操作

在多线程要操作一个字符串时,为了保证只有一个线程在赋值或者取值的时候我们会考虑加锁。

  • atomic属性内部的锁称为 自旋锁
  • 凡是线程安全的对象,内部肯定会加锁
@property(atomic,copy)NSString *name;

那么atomic是应该怎么在setter和getter中实现呢,原理是仅需要给self加锁,重写setter,getter:

@synthesize name = _name;- setName:(NSString *)name { @synchronized { _name = [name copy]; }}- (NSString *)name { @synchronized { return _name; }}

不过这么写,也不能保证线程安全。如果线程A调用了getter,同时线程B调用了setter,那么A线程getter得到的值,可能是B在set之前的原始值,也可能是B set的值。同时这个属性的值,也可能是B set的值。所以,保证数据完整性不能简单靠一把锁来完成,毕竟这个是多线程编程最大的难点。

8.synchronized

我们最常用的一种加锁机制,其实效率是最低的。他可以让我们不需要显示的去生成锁,而是系统自动生成锁。

[_lock lock];

[_elements addObject:element];

[_lock unlock];

用synchronized去实现:

@synchronized (obj 一个对象) {

        [_elements addObject:element];

}

@synchronized 如何将一个锁和你正在同步的对象关联起来:

当你调用 objc_sync_enter(obj) 时,它用 obj 内存地址的哈希值查找合适的 SyncData,然后将其上锁。当你调用 objc_sync_exit(obj) 时,它查找合适的 SyncData 并将其解锁。

bool lock = false; // 一开始没有锁上,任何线程都可以申请锁
do {
    while(test_and_set(&lock); // test_and_set 是一个原子操作
        Critical section  // 临界区
    lock = false; // 相当于释放锁,这样别的线程可以进入临界区
        Reminder section // 不需要锁保护的代码        
}

锁的性能比较

性能从高到低排序

  • 1、os_unfair_lock
  • 2、OSSpinLock
  • 3、dispatch_semaphore
  • 4、pthread_mutex
  • 5、dispatch_queue(DISPATCH_QUEUE_SERIAL)
  • 6、NSLock
  • 7、NSCondition
  • 8、pthread_mutex(recursive)
  • 9、NSRecursiveLock
  • 10、NSConditionLock
  • 11、@synchronized

3.同步会消耗性能

无论是加锁还是使用原子操作都会消耗系统的性能,因此讨论各种同步方法的性能消耗就变得非常重要。

加锁操作伴随的消耗:

从用户态切换到内核态。上下文切换。上下文切换的要点:

1)进程上下文切换可以描述为kernel执行下面的操作

a. 挂起一个进程,并储存该进程当时寄存器和程序计数器的状态

b. 从内存中恢复下一个要执行的进程,恢复该进程原来的状态到寄存器,返回到其上次暂停的执行代码然后继续执行

2)上下文切换只能发生在内核态,所以还会触发用户态与内核态切换

OSSpinLock(性能最高但不再安全 具体使用方式也无须过于了解,iOS SDK里无OSSpinLock头文件可导 用不了)>

13种锁

OSSpinLock叫做”自旋锁”,使用时需要导入头文件#import <libkern/OSAtomic.h>

//初始化OSSpinLock lock = OS_SPINLOCK_INIT;//加锁OSSpinLockLock(&lock);//解锁OSSpinLockUnlock(&lock);

demo

#import "OSSpinLockDemo.h"#import <libkern/OSAtomic.h>@interface OSSpinLockDemo()@property (assign, nonatomic) OSSpinLock ticketLock;@end@implementation OSSpinLockDemo- (instancetype)init{self = [super init];if  {self.ticketLock = OS_SPINLOCK_INIT;}return self;}//卖票- sellingTickets{OSSpinLockLock(&_ticketLock);[super sellingTickets];OSSpinLockUnlock(&_ticketLock);}@end

图片 2

OSSpinLock在iOS10.0以后就被弃用了,可以使用os_unfair_lock_lock替代。而且还有一些安全性问题,具体参考不再安全的 OSSpinLock

os_unfair_lock用于取代不安全的OSSpinLock,从iOS10开始才支持从底层调用看,等待os_unfair_lock锁的线程会处于休眠状态,并非忙等需要导入头文件#import <os/lock.h>

//初始化os_unfair_lock lock = OS_UNFAIR_LOCK_INIT;//加锁os_unfair_lock_lock(&lock);//解锁os_unfair_lock_unlock(&lock);

demo

#import "os_unfair_lockDemo.h"#import <os/lock.h>@interface os_unfair_lockDemo()@property (assign, nonatomic) os_unfair_lock ticketLock;@end@implementation os_unfair_lockDemo- (instancetype)init{self = [super init];if  {self.ticketLock = OS_UNFAIR_LOCK_INIT;}return self;}//卖票- sellingTickets{os_unfair_lock_lock(&_ticketLock);[super sellingTickets];os_unfair_lock_unlock(&_ticketLock);}@end

mutex叫做”互斥锁”,等待锁的线程会处于休眠状态。需要导入头文件#import <pthread.h>使用步骤

  • 1、初始化锁的属性
pthread_mutexattr_t attr;pthread_mutexattr_init(&attr);pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);/** Mutex type attributes*/#define PTHREAD_MUTEX_NORMAL 0#define PTHREAD_MUTEX_ERRORCHECK 1#define PTHREAD_MUTEX_RECURSIVE 2#define PTHREAD_MUTEX_DEFAULT PTHREAD_MUTEX_NORMAL
  • 2、初始化锁
// 初始化锁pthread_mutex_init(mutex, &attr);
  • 3、初始化锁结束以后,销毁属性
// 销毁属性pthread_mutexattr_destroy(&attr);
  • 4、加锁解锁
pthread_mutex_lock(&_mutex);pthread_mutex_unlock(&_mutex);
  • 5、销毁锁
pthread_mutex_destroy(&_mutex);

备注:我们可以不初始化属性,在传属性的时候直接传NULL,表示使用默认属性PTHREAD_MUTEX_NORMALpthread_mutex_init(mutex, NULL);

具体代码

#import "pthread_mutexDemo.h"#import <pthread.h>@interface pthread_mutexDemo()@property (assign, nonatomic) pthread_mutex_t ticketMutex;@end@implementation pthread_mutexDemo- (instancetype)init{self = [super init];if  {// 初始化属性pthread_mutexattr_t attr;pthread_mutexattr_init(&attr);pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_DEFAULT);// 初始化锁pthread_mutex_init(&(_ticketMutex), &attr);// 销毁属性pthread_mutexattr_destroy(&attr);}return self;}//卖票- sellingTickets{pthread_mutex_lock(&_ticketMutex);[super sellingTickets];pthread_mutex_unlock(&_ticketMutex);}@end

死锁我们稍微的修改一下代码

//卖票- sellingTickets{pthread_mutex_lock(&_ticketMutex);[super sellingTickets];[self sellingTickets2];pthread_mutex_unlock(&_ticketMutex);}- sellingTickets2{pthread_mutex_lock(&_ticketMutex);NSLog(@"%s",__func__);pthread_mutex_unlock(&_ticketMutex);}

图片 3

上面的代码就会造成线程死锁,因为方法sellingTickets的结束需要sellingTickets2解锁,方法sellingTickets2的结束需要sellingTickets解锁,相互引用造成死锁

但是pthread_mutex_t里面有一个属性可以解决这个问题PTHREAD_MUTEX_RECURSIVE

PTHREAD_MUTEX_RECURSIVE 递归锁:允许同一个线程对同一把锁进行重复加锁。要考重点同一个线程同一把锁

- (instancetype)init{self = [super init];if  {// 初始化属性pthread_mutexattr_t attr;pthread_mutexattr_init(&attr);pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);// 初始化锁pthread_mutex_init(&(_ticketMutex), &attr);// 销毁属性pthread_mutexattr_destroy(&attr);}return self;}

对于上面的问题还有一个解决方案就是在方法sellingTickets2中重新在创建一把新的锁,两个方法的锁对象不同,就不会造成线程死锁了。

图片 4

条件

// 初始化属性pthread_mutexattr_t attr;pthread_mutexattr_init(&attr);pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);// 初始化锁pthread_mutex_init(&_mutex, &attr);// 销毁属性pthread_mutexattr_destroy(&attr);// 初始化条件pthread_cond_t conditionpthread_cond_init(&_cond, NULL);// 等待条件pthread_cond_wait(&_cond, &_mutex);//激活一个等待该条件的线程pthread_cond_signal(&_cond);//激活所有等待该条件的线程pthread_cond_broadcast(&_cond);//销毁资源pthread_mutex_destroy(&_mutex);pthread_cond_destroy(&_cond);

使用案例:假设我们有一个数组,里面有两个线程,一个是添加数组,一个是删除数组,我们先调用删除数组,在调用添加数组,但是在数组为空的时候不调用删除数组。

#import "pthread_mutexDemo1.h"#import <pthread.h>@interface pthread_mutexDemo1()@property (assign, nonatomic) pthread_mutex_t mutex;@property (assign, nonatomic) pthread_cond_t cond;@property (strong, nonatomic) NSMutableArray *data;@end@implementation pthread_mutexDemo1- (instancetype)init{if (self = [super init]) {// 初始化属性pthread_mutexattr_t attr;pthread_mutexattr_init(&attr);pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);// 初始化锁pthread_mutex_init(&_mutex, &attr);// 销毁属性pthread_mutexattr_destroy(&attr);// 初始化条件pthread_cond_init(&_cond, NULL);self.data = [NSMutableArray array];}return self;}- otherTest{[[[NSThread alloc] initWithTarget:self selector:@selector object:nil] start];[[[NSThread alloc] initWithTarget:self selector:@selector object:nil] start];}// 线程1// 删除数组中的元素- __remove{pthread_mutex_lock(&_mutex);NSLog(@"__remove - begin");if (self.data.count == 0) {// 等待pthread_cond_wait(&_cond, &_mutex);}[self.data removeLastObject];NSLog;pthread_mutex_unlock(&_mutex);}// 线程2// 往数组中添加元素- __add{pthread_mutex_lock(&_mutex);sleep;[self.data addObject:@"Test"];NSLog;// 激活一个等待该条件的线程pthread_cond_signal(&_cond);pthread_mutex_unlock(&_mutex);}- dealloc{pthread_mutex_destroy(&_mutex);pthread_cond_destroy(&_cond);}

为了准确测试我们可以在__addsleep

图片 5

NSLock是对mutex普通锁的封装。pthread_mutex_init(mutex, NULL);

NSLock 遵循 NSLocking 协议。Lock 方法是加锁,unlock 是解锁,tryLock 是尝试加锁,如果失败的话返回 NO,lockBeforeDate: 是在指定Date之前尝试加锁,如果在指定时间之前都不能加锁,则返回NO

@protocol NSLocking- lock;- unlock;@end@interface NSLock : NSObject <NSLocking> {@privatevoid *_priv;}- tryLock;- lockBeforeDate:limit;@property (nullable, copy) NSString *name@end

使用起来也是十分的简单

#import "LockDemo.h"@interface LockDemo()@property (strong, nonatomic) NSLock *ticketLock;@end@implementation LockDemo//卖票- sellingTickets{[self.ticketLock lock];[super sellingTickets];[self.ticketLock unlock];}@end

NSRecursiveLock是对mutex递归锁的封装,API跟NSLock基本一致

#import "RecursiveLockDemo.h"@interface RecursiveLockDemo()@property (nonatomic,strong) NSRecursiveLock *ticketLock;@end@implementation RecursiveLockDemo//卖票- sellingTickets{[self.ticketLock lock];[super sellingTickets];[self.ticketLock unlock];}@end

NSCondition是对mutexcond的封装,更加面向对象,我们使用起来也更加的方便简洁

@interface NSCondition : NSObject <NSLocking> {- wait;- waitUntilDate:limit;- signal;- broadcast;@property (nullable, copy) NSString *name @end

对于上面那个数组操作的案例我们就可以变成这个样子了

// 线程1// 删除数组中的元素- __remove{[self.condition lock];if (self.data.count == 0) {// 等待[self.condition wait];}[self.data removeLastObject];NSLog;[self.condition unlock];}// 线程2// 往数组中添加元素- __add{[self.condition lock];sleep;[self.data addObject:@"Test"];NSLog;// 信号[self.condition signal];[self.condition unlock];}

NSConditionLock是对NSCondition的进一步封装,可以设置具体的条件值

@interface NSConditionLock : NSObject <NSLocking> { - (instancetype)initWithCondition:(NSInteger)condition;@property  NSInteger condition;- lockWhenCondition:(NSInteger)condition;- tryLock;- tryLockWhenCondition:(NSInteger)condition;- unlockWithCondition:(NSInteger)condition;- lockBeforeDate:limit;- lockWhenCondition:(NSInteger)condition beforeDate:limit;@property (nullable, copy) NSString *name;@end

里面有三个常用的方法

  • 1、initWithCondition:初始化Condition,并且设置状态值
  • 2、lockWhenCondition:(NSInteger)condition:当状态值为condition的时候加锁
  • 3、unlockWithCondition:(NSInteger)condition当状态值为condition的时候解锁
@interface NSConditionLockDemo()@property (strong, nonatomic) NSConditionLock *conditionLock;@end@implementation NSConditionLockDemo- (instancetype)init{if (self = [super init]) {self.conditionLock = [[NSConditionLock alloc] initWithCondition:1];}return self;}- otherTest{[[[NSThread alloc] initWithTarget:self selector:@selector object:nil] start];[[[NSThread alloc] initWithTarget:self selector:@selector object:nil] start];}- __one{[self.conditionLock lock];NSLog;sleep;[self.conditionLock unlockWithCondition:2];}- __two{[self.conditionLock lockWhenCondition:2];NSLog;[self.conditionLock unlockWithCondition:3];}@end

图片 6

  • semaphore叫做”信号量”
  • 信号量的初始值,可以用来控制线程并发访问的最大数量
  • 信号量的初始值为1,代表同时只允许1条线程访问资源,保证线程同步
//表示最多开启5个线程dispatch_semaphore_create;// 如果信号量的值 > 0,就让信号量的值减1,然后继续往下执行代码// 如果信号量的值 <= 0,就会休眠等待,直到信号量的值变成>0,就让信号量的值减1,然后继续往下执行代码dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);// 让信号量的值 1dispatch_semaphore_signal(self.semaphore);

@interface dispatch_semaphoreDemo()@property (strong, nonatomic) dispatch_semaphore_t semaphore;@end@implementation dispatch_semaphoreDemo- (instancetype)init{if (self = [super init]) {self.semaphore = dispatch_semaphore_create;}return self;}- otherTest{for (int i = 0; i < 20; i  ) {[[[NSThread alloc] initWithTarget:self selector:@selector object:nil] start];}}- test{// 如果信号量的值 > 0,就让信号量的值减1,然后继续往下执行代码// 如果信号量的值 <= 0,就会休眠等待,直到信号量的值变成>0,就让信号量的值减1,然后继续往下执行代码dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);sleep;NSLog(@"test - %@", [NSThread currentThread]);// 让信号量的值 1dispatch_semaphore_signal(self.semaphore);}@end

我们在运行代码打印的时候发现,每隔一秒出现一次打印。虽然我们同时开启20个线程,但是一次只能访问一条线程的资源

使用GCD的串行队列也可以实现线程同步的

dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);dispatch_sync(queue, ^{// 追加任务1for (int i = 0; i < 2;   i) {NSLog(@"1---%@",[NSThread currentThread]);}});dispatch_sync(queue, ^{// 追加任务2for (int i = 0; i < 2;   i) {NSLog(@"2---%@",[NSThread currentThread]);}});

@synchronized是对mutex递归锁的封装,@synchronized内部会生成obj对应的递归锁,然后进行加锁、解锁操作

//卖票- sellingTickets{@synchronized ([self class]) {[super sellingTickets];}}

对是实现底层我们可以在objc4的objc-sync.mm文件中找到``synchronized就是在开始和结束的时候调用了objc_sync_enter&objc_sync_exit方法。

objc_sync_enter实现

int objc_sync_enter{int result = OBJC_SYNC_SUCCESS;if  {SyncData* data = id2data(obj, ACQUIRE);assert;data->mutex.lock();} else {// @synchronized does nothingif (DebugNilSync) {_objc_inform("NIL SYNC DEBUG: @synchronized; set a breakpoint on objc_sync_nil to debug");}objc_sync_nil();}return result;}

就是根据id2data方法找到一个data对象,然后在对data对象进行mutex.lock()加锁操作。我们点击进入id2data方法继续查找

#define LIST_FOR_OBJ sDataLists[obj].datastatic StripedMap<SyncList> sDataLists;

发现获取data对象的方法其实就是根据sDataLists[obj].data这个方法来实现的,也就是一个哈希表。

关于 @synchronized,这儿比你想知道的还要多

  • atomic用于保证属性setter、getter的原子性操作,相当于在getter和setter内部加了线程同步的锁
  • 可以参考源码objc4的objc-accessors.mm
  • 它并不能保证使用属性的过程是线程安全的

pthread_rwlock经常用于文件等数据的读写操作,需要导入头文件#import <pthread.h>

iOS中的读写安全方案需要注意一下场景

  • 1、同一时间,只能有1个线程进行写的操作
  • 2、同一时间,允许有多个线程进行读的操作
  • 3、同一时间,不允许既有写的操作,又有读的操作
//初始化锁pthread_rwlock_t lock;pthread_rwlock_init(&_lock, NULL);//读加锁pthread_rwlock_rdlock(&_lock);//读尝试加锁pthread_rwlock_trywrlock(&_lock)//写加锁pthread_rwlock_wrlock(&_lock);//写尝试加锁pthread_rwlock_trywrlock(&_lock)//解锁pthread_rwlock_unlock(&_lock);//销毁pthread_rwlock_destroy(&_lock);

#import <pthread.h>@interface pthread_rwlockDemo ()@property (assign, nonatomic) pthread_rwlock_t lock;@end@implementation pthread_rwlockDemo- (instancetype)init{self = [super init];if  {// 初始化锁pthread_rwlock_init(&_lock, NULL);}return self;}- otherTest{dispatch_queue_t queue = dispatch_get_global_queue;for (int i = 0; i < 10; i  ) {dispatch_async(queue, ^{[self read];});dispatch_async(queue, ^{[self write];});}}- read {pthread_rwlock_rdlock(&_lock);sleep;NSLog(@"%s", __func__);pthread_rwlock_unlock(&_lock);}- write{pthread_rwlock_wrlock(&_lock);sleep;NSLog(@"%s", __func__);pthread_rwlock_unlock(&_lock);}- dealloc{pthread_rwlock_destroy(&_lock);}@end

图片 7

我们可以发现读操作1s有可能出现多次,但是写操作不会

这个函数传入的并发队列必须是自己通过dispatch_queue_cretate创建的如果传入的是一个串行或是一个全局的并发队列,那这个函数便等同于dispatch_async函数的效果

//初始化self.queue = dispatch_queue_create("rw_queue", DISPATCH_QUEUE_CONCURRENT);//读操作dispatch_async(self.queue, ^{});//写操作dispatch_barrier_async(self.queue, ^{ });

1.什么是保证线程的安全,线程为什么会不安全

多个线程同时修改同一资源时可能会以意想不到的方式造成互相干扰,比如一个线程可能覆盖了另一个线程改动的地方,造成数据的错乱。

在设计多线程操作的时候,应该尽量避免线程的交互,如果必须要交互,应该使用同步工具,保证线程交互的时候是安全的。

//主线程中
    NSConditionLock *theLock = [[NSConditionLock alloc] init];

    //线程1
    dispatch_async(self.concurrentQueue, ^{
        for (int i=0;i<=3;i  )
        {
            [theLock lock];
            NSLog(@"thread1:%d",i);
            sleep(1);
            [theLock unlockWithCondition:i];
        }
    });

    //线程2
    dispatch_async(self.concurrentQueue, ^{
        [theLock lockWhenCondition:2];
        NSLog(@"thread2");
        [theLock unlock];
    });

为什么要线程安全

多个线程访问同一块资源的时候,很容易引发数据混乱问题。一个大家都喜欢拿来举例子的就是买票demo,今天我使用这个案例假设有100张票,同时开5个窗口买票,5个窗口买票,我们来看看结果

//卖票演示- ticketTest{self.ticketsCount = 50;dispatch_queue_t queue = dispatch_get_global_queue;for (NSInteger i = 0; i < 5; i  ) {dispatch_async(queue, ^{for (int i = 0; i < 10; i  ) {[self sellingTickets];}});}}//卖票- sellingTickets{int oldMoney = self.ticketsCount;sleep;oldMoney -= 1;self.ticketsCount = oldMoney;NSLog(@"当前剩余票数-> %d", oldMoney);}

图片 8

正常情况下我有50张票,然后卖了50次,剩余票数应该是0,但是打印结果竟然是3,所以这里就存在了线程安全问题。

出现线程安全的原因

图片 9

出现线程安全的原因就是在同一个时间,多个线程同时读取一个值,像线程A和B同时读取了当前票数为10,等于是卖了两张票,但是总票数其实就减少了一张。

解决方法

使用线程同步技术,按照预定的先后次序依次进行,常见的线程同步技术就是加锁

图片 10

5.NSRecursiveLock递归锁

NSRecursiveLock实际上定义的是一个递归锁,这个锁可以被同一线程多次请求,而不会引起死锁。它可以允许同一线程多次加锁,而不会造成死锁。递归锁会跟踪它被lock的次数。每次成功的lock都必须平衡调用unlock操作。只有所有达到这种平衡,锁最后才能被释放,以供其它线程使用。基于NSLock

NSRecursiveLock (递归锁)

目录

3.dispatch_semaphore

dispatch_semaphore_create(long value)生成一个值为value的dispatch_semaphore_t类型的信号量。

dispatch_semaphore_signal(dispatch_semaphore_t deem)

这个方法会使输入的deem信号量的值加1 

long dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);

计数大于等于1时不等待并返回该函数,并且计数减1,计数等于0时等待,等待什么呢,如果在timeout时间内这个信号量的值大于0了(信号量值加1了),这个时候就返回该函数向下执行,如果等到了timeout信号量的值依然没有大于0,就继续执行下面的操作。

这个函数的返回结果是long类型的,如果返回的是0代表信号量的值大于0可以执行,如果非0,代表信号量的值等于0,不能执行。

信号量是睡眠等待的,假设有两个线程,线程a和线程b,分别运行在core0和core1上,线程a想要访问一段临界区的代码,但是锁正好被线程b拿着,这个时候线程a不会一直在corea上发请求,corea会把线程a放到等待队列,直到这个锁被b释放了再执行线程a。

//设置票的数量为5
    _tickets = 5;

    //线程1
    dispatch_async(self.concurrentQueue, ^{
        [self saleTickets];
    });

    //线程2
    dispatch_async(self.concurrentQueue, ^{
        [self saleTickets];
    });

- (void)saleTickets
{
    while (1) {
        @synchronized(self) {
            [NSThread sleepForTimeInterval:1];
            if (_tickets > 0) {
                _tickets--;
                NSLog(@"剩余票数= %ld, Thread:%@",_tickets,[NSThread currentThread]);
            } else {
                NSLog(@"票卖完了  Thread:%@",[NSThread currentThread]);
                break;
            }
        }
    }
}
  • 1、为什么要线程安全
  • 2、自旋锁和互斥锁
  • 3、锁的类型
    • 1、OSSpinLock
    • 2、os_unfair_lock
    • 3、pthread_mutex
    • 4、dispatch_semaphore
    • 5、dispatch_queue(DISPATCH_QUEUE_SERIAL)
    • 6、NSLock
    • 7、NSRecursiveLock
    • 8、NSCondition
    • 9、NSConditionLock
    • 10、@synchronized
    • 11、pthread_rwlock
    • 12、dispatch_barrier_async
    • 13、atomic
  • 4、锁的性能比较

4.NSLock

NSLock是oc以对象的形式暴露给开发者的一种锁,内部封装pthread_mutex

常用的四种方法:

NSLock *lock = [[NSLock alloc] init];

[lock lock]; 加锁   [lock unlock]; 解锁  

[lock tryLock];   尝试获取锁

NSDate *date = [[NSDate alloc] initWithTimeIntervalSinceNow:3];

[lock lockBeforeDate:date];在所指定的date时间之前尝试获取锁

//创建锁
    _rsLock = [[NSRecursiveLock alloc] init];

   //线程1
    dispatch_async(self.concurrentQueue, ^{
        static void(^TestMethod)(int);
        TestMethod = ^(int value)
        {
            [_rsLock lock];
            if (value > 0)
            {
                [NSThread sleepForTimeInterval:1];
                TestMethod(value--);
            }
            [_rsLock unlock];
        };

        TestMethod(5);
    });

4.自旋锁

自旋锁是为了保护一小段临界区代码,保证这个临界区的操作是原子的,从而避免并发的竞争。如果内核控制路径发现自旋锁开着,就获得自旋锁并执行自己的操作,如果内核控制路径发现自旋锁被另一个cpu上的内核控制路径使用,就等待,等待的过程是忙等,直到自旋锁被释放。所以自旋锁保护的代码段必须非常小,否则等待自旋锁的释放会消耗很多时间。

⚠️:什么是忙等busy-waiting

假设有两个线程,线程a和线程b,分别运行在core0和core1上,如果线程a想通过pthread_spin_lock操纵去得到临界区的锁,而这个锁正在被线程b持有,那么这个时候,core0就会一直运行线程a,线程a一直进行锁请求,直到得到这个锁。

OSSpinLock

__block OSSpinLock theLock = OS_SPINLOCK_INIT;

OSSpinLockLock(&theLock);上锁   OSSpinLockUnlock(&theLock);开锁

⚠️:有人发现自旋锁是不再安全的,因为低优先级线程拿到锁时,高优先级线程进入忙等状态,消耗大量cpu时间,导致低优先级拿不到cpu时间,无法完成任务并释放锁,就一直持有锁,高优先级无法拿到锁。产生了优先级反转。

如果临界区的代码非常少,那么自旋锁的执行效率是很高的。

dispatch_semaphore 信号量实现加锁

2.几种常见的同步工具

原子操作 atomic属性,默认系统生成的属性是原子的。原子属性在setter和getter方法上是线程安全的。如果是非原子属性,同时在不同的线程调用setter方法就会产生问题。原子操作其实是用自旋锁实现的。

锁是最常见的同步工具,用来保护临界区(critical section),这些代码段在同一时间只能允许被一个线程访问。这些代码段可能要求同一时间只能有一个用户执行操作,这种代码就需要用锁去保护。

条件 

__block pthread_mutex_t mutex;
    pthread_mutex_init(&mutex, NULL);

    //线程1
    dispatch_async(self.concurrentQueue), ^{
        pthread_mutex_lock(&mutex);
        NSLog(@"任务1");
        sleep(2);
        pthread_mutex_unlock(&mutex);
    });

    //线程2
    dispatch_async(self.concurrentQueue), ^{
        sleep(1);
        pthread_mutex_lock(&mutex);
        NSLog(@"任务2");
        pthread_mutex_unlock(&mutex);
    });

2.互斥锁pthread_mutex

⚠️:信号量用于线程的同步,互斥锁用于线程的互斥。

信号量用在多线程多任务同步的,一个线程完成了某一个动作就通过信号量告诉别的线程,别的线程再进行某些动作(大家都在semtake的时候,就阻塞在 哪里)。而互斥锁是用在多线程多任务互斥的,一个线程占用了某一个资源,那么别的线程就无法访问,直到这个线程unlock,其他的线程才开始可以利用这个资源。

哈哈哈我今天终于明白了互斥锁和信号量是不一样的,互斥锁是为了把一个代码块锁住,上锁时,其他任何线程都不能访问被保护的资源。

但是信号量是不一样的,他可以让多个线程安全的同步执行。

互斥的值只能是0或1,信号量的值可以为非负整数。

也就是说,一个互斥量只能用于一个资源的互斥访问,它不能实现多个资源的多线程互斥问题。信号量可以实现多个同类资源的多线程互斥和同步。当信号量为单值信号量是,也可以完成一个资源的互斥访问。

互斥量的加锁和解锁必须由同一线程分别对应使用,信号量可以由一个线程释放,另一个线程得到。

互斥锁的使用:

创建:CreateMutex、加锁:pthread_mutex_lock、解锁:pthread_mutex_unlock、销毁pthread_mutex_destroy

以下列举简单使用的买票实例 http://www.jianshu.com/p/1e59f0970bf5 (从此文章中粘入几个实例引用便于自己观看回顾)

7.NSCondition

常用的方法:是一种基于信号量的实现方法

NSCondition *lock = [[NSCondition alloc] init];

[lock lock];上锁

[lock unlock];解锁

[lock wait];这个方法比较特殊,调用之后当前线程直接进入 wait 状态,当其它线程中的该锁执行 signal 或者 broadcast 方法时,线程被唤醒,继续运行之后的方法。

[lock signal]; 可以唤醒一个等待的线程

[lock broadcast];可以唤醒所有等待的线程

NSCondition和信号量的区别:

从上面的实例代码可以看到,一个 dispatch_semaphore_wait(signal, overTime); 方法会去对应一个 dispatch_semaphore_signal(signal); 看起来像NSLock的 lock 和 unlock,其实可以这样理解,区别只在于有信号量这个参数,lock unlock 只能同一时间,一个线程访问被保护的临界区,而如果 dispatch_semaphore 的信号量初始值为 x ,则可以有 x 个线程同时访问被保护的临界区。

// 创建信号量
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
    //线程1
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
         NSLog(@"任务1");
        sleep(10);
        dispatch_semaphore_signal(semaphore);
    });

    //线程2
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        sleep(1);
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"任务2");
        dispatch_semaphore_signal(semaphore);
    });

dispatch_semaphore (信号量加锁)> pthread_mutex (互斥锁)> NSLock (对象锁 也是互斥锁的一种)>NSRecursiveLock (递归锁) >NSConditionLock (条件锁)> @synchronized (互斥锁)

//设置票的数量为5
   _tickets = 5;

   //创建锁
   _mutexLock = [[NSLock alloc] init];

   //线程1
   dispatch_async(self.concurrentQueue, ^{
       [self saleTickets];
   });

   //线程2
   dispatch_async(self.concurrentQueue, ^{
       [self saleTickets];
   });

- (void)saleTickets
{

   while (1) {
       [NSThread sleepForTimeInterval:1];
       //加锁
       [_mutexLock lock];
       if (_tickets > 0) {
           _tickets--;
           NSLog(@"剩余票数= %ld, Thread:%@",_tickets,[NSThread currentThread]);        
       } else {
           NSLog(@"票卖完了  Thread:%@",[NSThread currentThread]);
           break;
       }
       //解锁
       [_mutexLock unlock];
   }
}

NSLock 互斥锁只是在内部封装了一个 pthread_mutex,属性为PTHREAD_MUTEX_ERRORCHECK,它会损失一定性能换来错误提示。不能多次调用lock

pthread_mutex 互斥锁 需要导入#include <pthread.h>

对于自旋锁和互斥锁二者的区别
相同点在于:都能保证同一时间只有一个线程访问共享资源 保证安全
不同点在于:1自旋锁效率高于互斥锁
2如果共享数据已有其他线程加锁了,互斥锁会使线程进入休眠状态等待解锁,如果资源解锁,则线程被唤醒。而自旋锁会使线程以死循环的方式等待解锁,如果资源解锁,则另一个线程会立即执行

PS:atomic内部为互斥锁 ->其中setter方法中多了个@synchronized(self){} <-等当前对象操作完毕会合成确定值

首先要知道锁是一种同步机制,用于在存在多线程环境中实施对资源访问限制。其中有互斥锁、对象所、递归锁、条件锁、自旋锁、信号量实现加锁这几种。他们分别有自己的处理机制故而性能也不尽相同,具体性能由快到慢简单总结如下:

@synchronized 是一个 OC 层面的锁, 主要是通过牺牲性能换来语法上的简洁,@synchronized 后面需要紧跟一个 OC 对象,它实际上是把这个对象当做锁来使用。这是通过一个哈希表来实现的,OC 在底层使用了一个互斥锁的数组(你可以理解为锁池),通过对对象去哈希值来得到对应的互斥锁。

自旋锁

基本思路就三点:1:iOS中有几种常用的锁

             2:几种锁的性能及个别锁的简单使用 

             3:对于常用锁中互斥锁(NSLock 和@synchronized)和自旋锁的区别

由于之前开发中用到了锁这个东西,加上这个知识在之前的项目中用之甚少,所以对于此想做一下在百度网上文章过程中的再一次了解中的简单总结。

本文由新葡京8455发布,转载请注明来源

关键词: