概述

多线程的本质就是CPU轮流随机分配给每条线程时间片资源执行任务,看起来多条线程同时执行任务。



多条线程同时访问同一块资源,比如操作同一个对象、统一变量、同一个文件,就会引发数据错乱和数据安全的问题。

多线程引发问题实例

这里我也借助网上两个比较经典的案例,卖票和存取钱。

卖票案例

多个线程同时卖票,同一时间多个线层同时访问票的总数,就会引发数据错乱。

实例代码

@interface ViewController ()
@property (nonatomic, assign) int tickets;
@end @implementation ViewController - (void)viewDidLoad {
[super viewDidLoad]; self.tickets = 30;
[self saleTicketTest];
} - (void)saleTicketWithName:(NSString *)name
{
int currTicketCount = self.tickets;
sleep(.2);
currTicketCount--;
self.tickets = currTicketCount;
NSLog(@"当前%@卖了一张,票还剩:%d张", name ,self.tickets);
} - (void)saleTicketTest
{
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
for (int i=0; i<10; i++) {
[self saleTicketWithName:@"A"];
}
}); dispatch_async(queue, ^{
for (int i=0; i<10; i++) {
[self saleTicketWithName:@"B"];
}
}); dispatch_async(queue, ^{
for (int i=0; i<10; i++) {
[self saleTicketWithName:@"C"];
}
});
}
@end

异常结果:

存钱取钱案例

实例代码

#import "ViewController.h"
@interface ViewController ()
@property (nonatomic, assign) int currMoney;
@end @implementation ViewController - (void)viewDidLoad {
[super viewDidLoad]; self.currMoney = 1000;
[self moneyTest];
} - (void)moneyTest
{ dispatch_async(dispatch_get_global_queue(0, 0), ^{
for (int i=0; i<4; i++) {
[self saveMoney];
}
}); dispatch_async(dispatch_get_global_queue(0, 0), ^{
for (int i=0; i<4; i++) {
[self drawMoney];
}
});
} // 存钱
- (void)saveMoney
{
int money = self.currMoney;
sleep(.3);
money += 100;
self.currMoney = money;
NSLog(@"存了100,还剩%d元", self.currMoney); } // 取钱
- (void)drawMoney
{
int money = self.currMoney;
sleep(.3);
money -= 50;
self.currMoney = money;
NSLog(@"取了50,还剩%d元", self.currMoney);
}

异常结果:

多线程安全隐患的解决方案

开发中解决多线程安全隐患的方案-使用线程同步技术。同步(协同步调,按预定的先后顺序执行)

常见的线程同步技术就是加锁

iOS中线程同步的方案根据性能优先级排序:

线程同步的方案 需要导入头文件 类别 注意事项
os_unfair_lock <os/lock.h> 互斥锁 iOS10才开始提供,用来替代OSSpinLock
OSSpinLock <libkern/OSAtomic.h> 自旋锁 目前不在安全,可能会出现优先级反转问题
dispatch_semaphore #import <pthread.h>
pthread_mutex #import <pthread.h>
dispatch_queue(DISPATCH_SENTINEL) #import <pthread.h>
NSLock
oNSCondition #import <os/lock.h>
pthread_mutex(recuresive) #import <pthread.h>
NSRecursiveLock
NSConditionLock
@synchronized

OSSpinLock

OSSpinLock叫做”自旋锁”,等待锁的线程会处于忙等(busy-wait)状态,一直占用着CPU资源。如果等待锁的线程优先级较高,它会一直占用着CPU资源,优先级低的线程就无法释放锁。

需要导入头文件#import <libkern/OSAtomic.h>

os_unfair_lock

os_unfair_lock用于取代不安全的OSSpinLock ,从iOS10开始才支持,从底层调用看,等待os_unfair_lock锁的线程会处于休眠状态,并非忙等。

需要导入头文件#import <os/lock.h>

pthread_mutex

mutex叫做”互斥锁”,等待锁的线程会处于休眠状态

需要导入头文件#import <pthread.h>

注意:pthread_mutex在不使用时需要释放锁与属性。

在上面存钱取钱实例通过pthread_mutex实现线程安全。

#import "ViewController.h"
#import <pthread.h>
@interface ViewController ()
@property (nonatomic, assign) int currMoney;
@property (nonatomic, assign) pthread_mutex_t lock;
@property (nonatomic, assign) pthread_mutexattr_t attr;
@end @implementation ViewController - (void)viewDidLoad {
[super viewDidLoad]; // 初始化属性
// pthread_mutexattr_t att;
pthread_mutexattr_init(&_attr);
// 设置锁的类型
pthread_mutexattr_settype(& _attr, PTHREAD_MUTEX_DEFAULT); // 初始化lock
pthread_mutex_init(&_lock, & _attr); self.currMoney = 1000;
[self moneyTest];
} - (void)dealloc
{
pthread_mutex_destroy(& _lock);
pthread_mutexattr_destroy(&_attr);
} - (void)moneyTest
{
dispatch_async(dispatch_get_global_queue(0, 0), ^{
for (int i=0; i<4; i++) {
[self saveMoney];
}
}); dispatch_async(dispatch_get_global_queue(0, 0), ^{
for (int i=0; i<4; i++) {
[self drawMoney];
}
});
} // 存钱
- (void)saveMoney
{
pthread_mutex_lock(& _lock);
int money = self.currMoney;
sleep(.3);
money += 100;
self.currMoney = money;
NSLog(@"存了100,还剩%d元", self.currMoney);
pthread_mutex_unlock(& _lock);
} // 取钱
- (void)drawMoney
{
pthread_mutex_lock(& _lock);
int money = self.currMoney;
sleep(.3);
money -= 50;
self.currMoney = money;
NSLog(@"取了50,还剩%d元", self.currMoney);
pthread_mutex_unlock(& _lock);
}

pthread_mutex – 递归锁

递归锁的特点:允许同一个线程对同一把锁进行重复加锁,这里强调的是同一个线程。

递归锁在是使用场合是什么呢?

方法嵌套

- (void)test1
{
pthread_mutex_lock(&_lock);
NSLog(@"任务1");
[self test2];
pthread_mutex_unlock(&_lock);
} - (void)test2
{
pthread_mutex_lock(&_lock);
NSLog(@"任务2");
pthread_mutex_unlock(&_lock);
}

递归

- (void)test1
{
pthread_mutex_lock(&_lock);
NSLog(@"任务1");
[self test1];
pthread_mutex_unlock(&_lock);
}

pthread_mutex – 条件锁

多线程任务执行是没有顺序的,比如现在想实现一个消费者模式的多线程案例

当有生产者产出数据是才需要让消费者获取数据执行逻辑。由于生产与消费是多线程,可以在消费逻辑中加入判断,当没有数据时让消费线程休眠释放锁,生产者线程获取锁执行生产,当生产者有数据时发送信号通知消费者线程,消费者线程唤醒并重新再次加锁。

这是就需要用pthread_mutex条件锁。

实例代码

#import "ViewController.h"
#import <pthread.h>
@interface ViewController ()
@property (nonatomic, assign) pthread_mutex_t lock;
@property (nonatomic, assign) pthread_cond_t condition;
@property (nonatomic, strong) NSMutableArray *array;
@end @implementation ViewController - (void)viewDidLoad {
[super viewDidLoad]; self.array = [NSMutableArray array]; // 传入NUll 使用PTHREAD_MUTEX_DEFAULT类型的锁
pthread_mutex_init(&_lock, NULL); pthread_cond_init(&_condition, NULL); dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self remove];
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self add];
});
} // 删除数组中的元素
- (void)remove
{
pthread_mutex_lock(&_lock);
if (self.array.count == 0) {
// 执行wait 当前线层进入休眠,放开mutex锁,被唤醒后对mutex再次加锁
pthread_cond_wait(&_condition, &_lock);
}
NSLog(@"删除了元素");
[self.array removeLastObject];
pthread_mutex_unlock(&_lock);
} // 往数组添加元素
- (void)add
{
pthread_mutex_lock(&_lock);
[self.array addObject:@"test"]; // 发送信号 激活等待该条件的线程
pthread_cond_signal(&_condition);
// 广播 激活所有等待该条件的线程
// pthread_cond_broadcast(&_condition);
NSLog(@"添加了元素");
pthread_mutex_unlock(&_lock);
}

NSLock NSRecursiveLock

NSLock是对mutex普通锁的封装。遵守了协议。

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

NSCondition

NSCondition是对mutex和cond的封装。

实例代码

#import "ViewController.h"
@interface ViewController ()
@property (nonatomic, strong) NSMutableArray *array;
@property (nonatomic, strong) NSCondition *condition;
@end @implementation ViewController - (void)viewDidLoad {
[super viewDidLoad]; self.array = [NSMutableArray array]; self.condition = [[NSCondition alloc] init]; dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self remove];
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self add];
});
} // 删除数组中的元素
- (void)remove
{
// pthread_mutex_lock(&_lock);
[self.condition lock];
if (self.array.count == 0) {
// 执行wait 当前线层进入休眠,放开mutex锁,被唤醒后对mutex再次加锁
[self.condition wait];
}
NSLog(@"删除了元素");
[self.array removeLastObject];
[self.condition unlock];
// pthread_mutex_unlock(&_lock);
} // 往数组添加元素
- (void)add
{
// pthread_mutex_lock(&_lock);
[self.condition lock];
[self.array addObject:@"test"]; // 发送信号 激活等待该条件的线程
[self.condition signal];
// 广播 激活所有等待该条件的线程
[self.condition broadcast];
NSLog(@"添加了元素");
[self.condition unlock];
// pthread_mutex_unlock(&_lock);
}

NSConditionLock

NSConditionLock是对NSCondition的进一步封装,可以设置具体的条件值。可以让子线程按着指定的顺序执行。

示例代码:

dispatch_semaphore 信号量

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

dispatch_queue串行队列

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

@synchronized

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

iOS开发系列-线程同步技术的更多相关文章

  1. iOS开发系列-线程状态

    概述 线程从创建到销毁中间存在很多种状态. 线程的状态 通过NSThread创建一条线程,开发者需要负责线程的创建和执行,线程的销毁由系统决定.创建一个继承NSThread的FMThread类,重写d ...

  2. iOS开发系列--通知与消息机制

    概述 在多数移动应用中任何时候都只能有一个应用程序处于活跃状态,如果其他应用此刻发生了一些用户感兴趣的那么通过通知机制就可以告诉用户此时发生的事情.iOS中通知机制又叫消息机制,其包括两类:一类是本地 ...

  3. iOS开发系列--网络开发

    概览 大部分应用程序都或多或少会牵扯到网络开发,例如说新浪微博.微信等,这些应用本身可能采用iOS开发,但是所有的数据支撑都是基于后台网络服务器的.如今,网络编程越来越普遍,孤立的应用通常是没有生命力 ...

  4. iOS开发系列--通知与消息机制--转

    来自:http://www.cocoachina.com/ios/20150318/11364.html 概述 在多数移动应用中任何时候都只能有一个应用程序处于活跃状态,如果其他应用此刻发生了一些用户 ...

  5. iOS开发系列--数据存取

    概览 在iOS开发中数据存储的方式可以归纳为两类:一类是存储为文件,另一类是存储到数据库.例如前面IOS开发系列-Objective-C之Foundation框架的文章中提到归档.plist文件存储, ...

  6. iOS开发系列--并行开发其实很容易

    --多线程开发 概览 大家都知道,在开发过程中应该尽可能减少用户等待时间,让程序尽可能快的完成运算.可是无论是哪种语言开发的程序最终往往转换成汇编语言进而解释成机器码来执行.但是机器码是按顺序执行的, ...

  7. 【转】iOS开发系列--数据存取

    原文: http://www.cnblogs.com/kenshincui/p/4077833.html#SQLite 概览 在iOS开发中数据存储的方式可以归纳为两类:一类是存储为文件,另一类是存储 ...

  8. iOS开发系列之app的一天

    本文主要讲述我对 iOS 开发的一些理解,希望能通过 app 从启动到退出,将一些的知识整合起来,形成一条知识链,目前涉及到的知识点有 runloop.runtime.文件存储.界面布局.离线推送.内 ...

  9. iOS开发系列--App扩展开发

    概述 从iOS 8 开始Apple引入了扩展(Extension)用于增强系统应用服务和应用之间的交互.它的出现让自定义键盘.系统分享集成等这些依靠系统服务的开发变成了可能.WWDC 2016上众多更 ...

随机推荐

  1. 最大字段和--GSS1 MUSHROOM ORZ

    过于naive了= =作为一个知识点总结一下算了.主要就是合并.对于一个区间的最大字段和,可以分别事下面的两个区间的子段和,或者事左边的右边加右边的左边.然后搞一下 = = #include < ...

  2. JavaWeb学习篇之----自定义标签&&JSTL标签库详解

    今天来看一下自定义标签的内容,自定义标签是JavaWeb的一部分非常重要的核心功能,我们之前就说过,JSP规范说的很清楚,就是Jsp页面中禁止编写一行Java代码,就是最好不要有Java脚本片段,下面 ...

  3. centos 服务器编译安装apache+php

    1.检查服务器中是否自带httpd,如果/etc/httpd/httpd.conf,说明系统自带httpd服务,需要卸载或关闭服务,不要让他影响到本次安装的服务启动 可以用 service httpd ...

  4. .net API跨域

    在webconfig中的节点 <system.webServer> </system.webServer> 下添加 <httpProtocol><custom ...

  5. Delphi 与SQL编程

    Delphi 与SQL编程 SQL语言作为关系数据库管理系统中的一种通用的结构查询语言, 已经被众多的数据库管理系统所采用,如Oracle.Sybase.Informix等数据库管理系统,它们都支持S ...

  6. 暑假集训test-8-30

    这套题有毒,T1标程挂了,T2题面完全莫名其妙,T3没有告诉取模害我打了好久高精... A题. 统计每个数后面比它小的数的个数记作f把,操作一个数就是把它后面所有比它小的数和它的f清0,然后若是它到它 ...

  7. Java-Class-C:org.springframework.http.converter.StringHttpMessageConverter

    ylbtech-Java-Class-C:org.springframework.http.converter.StringHttpMessageConverter 1.返回顶部 1.1. impor ...

  8. 装hadoop的第一步,装ubuntu并换源并装jdk

    如何装ubuntu,这个自己百度.具体安装网站:http://www.ubuntu.com 我安装的是ubuntu Server版本的,然后是全英文安装.所以它的源自动定位到美国 下面是如何换源的,第 ...

  9. 2019 牛客多校第一场 E ABBA

    题目链接:https://ac.nowcoder.com/acm/contest/881/E 题目大意 问有多少个由 (n + m) 个 ‘A’ 和 (n + m) 个 ‘B’,组成的字符串能被分割成 ...

  10. [02]APUE:POSIX 正则库(#include <regex.h>)

    正则匹配流程: 声明一个 regex_t 类型的变量(结构体) regcomp 函数会将“正则匹配条件”写入此结构体,并编译成特定的二进制格式(加快匹配速度) 声明一个 regmatch_t 类型的变 ...