多线程 之 多线程的安全隐患

1、多线程的安全隐患原因

主要是因为资源共享。当一块资源可能会被多个线程共享时,也就是多个线程可能会访问同一块资源(比如多个线程访问同一个对象、同一个变量、同一个文件),很容易引发数据错乱和数据安全问题。

2、解决方案

使用线程同步技术(同步,就是协同步调,按预定的先后次序进行)
常见的线程同步技术是:加锁(加完锁之后记得要解锁)
gcd的串行队列也可以实现线程同步

以下都是可以保证线程安全的线程同步方案:
OSSpinLock
os_unfair_lock
pthread_mutex
dispatch_semaphore
dispatch_queue(DISPATCH_QUEUE_SERIAL)
NSLock
NSRecursiveLock
NSCondition
NSConditionLock
@synchronized

3、各种锁的说明

OSSpinLock
OSSpinLockLock是一种自旋锁(高级锁):在等待过程中不会睡眠,而是一直处于循环状态、忙等状态,等待过程中会占用cpu资源。

不过,现在OSSpinLockLock已经不推荐使用了(iOS10废弃)。
原因:可能会出现优先级反转问题。即:如果等待锁的线程优先级较高,它会一直占用着CPU资源,优先级低的线程就无法释放锁。
(假如有两个线程执行任务,线程1优先级高,线程2优先级低,当线程2先执行任务时,OSSpinLock加锁,cpu分配更多的资源执行线程1,导致线程1一直加锁失败,线程2一直无法解锁。)

//初始化
OSSpinLock lock = OS_SPINLOCK_INIT;
//尝试加锁(如果需要等待就不加锁,直接返回false;如果不需要等待就加锁,返回ture)
bool result = OSSpinLockTry(&lock);
//加锁
OSSpinLock(&lock);
//解锁
OSSpinLockUnlock(&lock);

os_unfair_lock
os_unfair_lock用于取代不安全的OSSpinLock。
os_unfair_lock是一种低级锁:在等待过程中会睡眠,这样的锁在唤起时候也会浪费很多的资源。

//初始化
os_unfair_lock lock = OS_UNFAIR_LOCK_INIT;
//尝试加锁,同OSSpinLock
bool result = os_unfair_lock_trylock(&lock);
//加锁
os_unfair_lock_lock(&lock);
//解锁
os_unfair_lock_unlock(&lock);

pthread_mutex

  • pthread_mutex是一个互斥锁。
    当设置属性为PTHREAD_MUTEX_NORMAL时候,锁就是默认default为互斥锁。
//初始化锁的属性
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL);
//初始化锁
pthread_mutex_t mutex;
pthread_mutex_init(&mutex, &attr);
//尝试加锁
pthread_mutex_trylock(&mutex);
//加锁
pthread_mutex_lock(&mutex);
//解锁
pthread_mutex_unlock(&mutex);
//销魂相关属性
pthread_mutexattr_destroy(&attr);
pthread_mutex_destroy(&mutex);
  • pthread_mutex:递归
    当设置属性为PTHREAD_MUTEX_RECURSIVE,为递归锁(属于互斥锁的一种特例),允许同一个线程在未释放其拥有的锁时反复对该锁进行加锁操作。
    // 初始化属性
    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_mutex_destroy(mutex);
  • pthread_mutex:条件
// 初始化属性
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL);
//初始化锁
pthread_mutex_init(&_mutex, &attr);
//销毁属性
pthread_mutexattr_destroy(&attr);
//初始化条件
pthread_cond_t _cond;
pthread_cond_init(&_cond, NULL);
//等待条件(进入休眠,放开mutex锁,被唤醒后,会再次对mutex加锁)
pthread_cond_wait(&_cond, &_mutex);
//激活一个等待该条件的线程
pthread_cond_signal(&_cond);
//激活所有等待该条件的线程
pthread_cond_broadcast(&_cond);
//销毁资源
pthread_mutex_destroy(&_mutex);
pthread_cond_destroy(&_cond);

NSLock、NSRecursiveLock、NSCondition
NSLock是对mutex普通锁的封装
NSRecursiveLock也是对mutex递归锁的封装
NSCondition是对mutex和cond的封装

//NSLock的API
NSLock *lock = [[NSLock alloc] init];//初始化锁
[lock lock];//上锁
[lock unlock];//解锁


//NSRecursiveLock的API
NSRecursiveLock *recursiveLock = [[NSRecursiveLock alloc] init];//初始化锁
[recursiveLock lock];//上锁
[recursiveLock unlock];//解锁


//NSCondition的API
self.condition = [[NSCondition alloc] init];//初始化
[self.condition wait];//等待条件
[self.condition signal];//激活一个等待该条件的线程
[self.condition broadcast]; //激活所有等待该条件的线程
[self.condition unlock];//解锁

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

//初始化
- (instancetype)initWithCondition:(NSInteger)condition;
- (void)lockWhenCondition:(NSInteger)condition;
- (BOOL)tryLock;
- (BOOL)tryLockWhenCondition:(NSInteger)condition;
- (void)unlockWithCondition:(NSInteger)condition;
- (BOOL)lockBeforeDate:(NSDate *)limit;
- (BOOL)lockWhenCondition:(NSInteger)condition beforeDate:(NSDate *)limit;

dispatch_queue (串行serial)
直接使用GCD的串行队列,也是可以实现线程同步的
因为GCD的串行队列访问同一资源的时候,本质就是一个一个按照顺序去执行的

dispatch_queue_t serialQueue = dispatch_queue_create("serialQueue", DISPATCH_QUEUE_SERIAL);
dispatch_sync(serialQueue, ^{
        
});

dispatch_semaphore
semaphore叫做”信号量”
信号量的初始值,可以用来控制线程并发访问的最大数量
信号量的初始值为1,代表同时只允许1条线程访问资源,保证线程同步

//初始化信号量
dispatch_semaphore_t  semaphore = dispatch_semaphore_create(long value);
// 如果信号量的值 > 0,就让信号量的值减1,然后继续往下执行代码
// 如果信号量的值 <= 0,就会休眠等待,直到信号量的值变成>0,就让信号量的值减1,然后继续往下执行代码
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
//让信号量的值+1
dispatch_semaphore_signal(semaphore);

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

@synchronized (self) {
     //任务
}

以上锁的使用实例子见github:https://github.com/ychen3022/lockDemo.git

4、总结
  • <1>以上各种锁的性能怎么样?
os_unfair_lock   //性能最好,但是iOS10才能用,是对os_unfair_lock的替代
OSSpinLock  //自旋锁,不建议使用,已经废弃了
dispatch_semaphore  //性能好,而且ios8、9都可以使用
pthread_mutex //互斥锁,推荐使用
dispatch_queue(DISPATCH_QUEUE_SERIAL)//gcd的串行队列
NSLock  //互斥锁,是对mutex的封装
NSCondition//互斥锁
pthread_mutex(recursive) //递归锁,相比mutext普通锁,要耗性能一些
NSRecursiveLock//互斥锁
NSConditionLock//对NSCondition的进一步封装,互斥锁
@synchronized//性能最差,是NSLock的一种封装,牺牲了效率,简洁了语法。
  • <2>自旋锁和互斥锁的对比?
    (1)、自旋锁:称之为高级锁,会忙等,不休眠。即在访问被锁资源时,调用者线程不会休眠,而是不停循环在那里,直到被锁资源释放锁。
    (2)、互斥锁:称之为低级锁,会休眠。即在访问被锁资源时,调用者线程会休眠,此时cpu可以调度其他线程工作。直到被锁资源释放锁。此时会唤醒休眠线程。

  • 什么情况使用自旋锁比较划算?
    预计线程等待锁的时间很短
    加锁的代码(临界区)经常被调用,但竞争情况很少发生
    CPU资源不紧张
    多核处理器

  • 什么情况使用互斥锁比较划算?
    预计线程等待锁的时间较长
    单核处理器
    临界区有IO(文件读写)操作,因为IO操作都耗资源
    临界区代码复杂或者循环量大
    临界区竞争非常激烈

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容