概述

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



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

多线程引发问题实例

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

卖票案例

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

实例代码

  1. @interface ViewController ()
  2. @property (nonatomic, assign) int tickets;
  3. @end
  4. @implementation ViewController
  5. - (void)viewDidLoad {
  6. [super viewDidLoad];
  7. self.tickets = 30;
  8. [self saleTicketTest];
  9. }
  10. - (void)saleTicketWithName:(NSString *)name
  11. {
  12. int currTicketCount = self.tickets;
  13. sleep(.2);
  14. currTicketCount--;
  15. self.tickets = currTicketCount;
  16. NSLog(@"当前%@卖了一张,票还剩:%d张", name ,self.tickets);
  17. }
  18. - (void)saleTicketTest
  19. {
  20. dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
  21. dispatch_async(queue, ^{
  22. for (int i=0; i<10; i++) {
  23. [self saleTicketWithName:@"A"];
  24. }
  25. });
  26. dispatch_async(queue, ^{
  27. for (int i=0; i<10; i++) {
  28. [self saleTicketWithName:@"B"];
  29. }
  30. });
  31. dispatch_async(queue, ^{
  32. for (int i=0; i<10; i++) {
  33. [self saleTicketWithName:@"C"];
  34. }
  35. });
  36. }
  37. @end

异常结果:

存钱取钱案例

实例代码

  1. #import "ViewController.h"
  2. @interface ViewController ()
  3. @property (nonatomic, assign) int currMoney;
  4. @end
  5. @implementation ViewController
  6. - (void)viewDidLoad {
  7. [super viewDidLoad];
  8. self.currMoney = 1000;
  9. [self moneyTest];
  10. }
  11. - (void)moneyTest
  12. {
  13. dispatch_async(dispatch_get_global_queue(0, 0), ^{
  14. for (int i=0; i<4; i++) {
  15. [self saveMoney];
  16. }
  17. });
  18. dispatch_async(dispatch_get_global_queue(0, 0), ^{
  19. for (int i=0; i<4; i++) {
  20. [self drawMoney];
  21. }
  22. });
  23. }
  24. // 存钱
  25. - (void)saveMoney
  26. {
  27. int money = self.currMoney;
  28. sleep(.3);
  29. money += 100;
  30. self.currMoney = money;
  31. NSLog(@"存了100,还剩%d元", self.currMoney);
  32. }
  33. // 取钱
  34. - (void)drawMoney
  35. {
  36. int money = self.currMoney;
  37. sleep(.3);
  38. money -= 50;
  39. self.currMoney = money;
  40. NSLog(@"取了50,还剩%d元", self.currMoney);
  41. }

异常结果:

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

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

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

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实现线程安全。

  1. #import "ViewController.h"
  2. #import <pthread.h>
  3. @interface ViewController ()
  4. @property (nonatomic, assign) int currMoney;
  5. @property (nonatomic, assign) pthread_mutex_t lock;
  6. @property (nonatomic, assign) pthread_mutexattr_t attr;
  7. @end
  8. @implementation ViewController
  9. - (void)viewDidLoad {
  10. [super viewDidLoad];
  11. // 初始化属性
  12. // pthread_mutexattr_t att;
  13. pthread_mutexattr_init(&_attr);
  14. // 设置锁的类型
  15. pthread_mutexattr_settype(& _attr, PTHREAD_MUTEX_DEFAULT);
  16. // 初始化lock
  17. pthread_mutex_init(&_lock, & _attr);
  18. self.currMoney = 1000;
  19. [self moneyTest];
  20. }
  21. - (void)dealloc
  22. {
  23. pthread_mutex_destroy(& _lock);
  24. pthread_mutexattr_destroy(&_attr);
  25. }
  26. - (void)moneyTest
  27. {
  28. dispatch_async(dispatch_get_global_queue(0, 0), ^{
  29. for (int i=0; i<4; i++) {
  30. [self saveMoney];
  31. }
  32. });
  33. dispatch_async(dispatch_get_global_queue(0, 0), ^{
  34. for (int i=0; i<4; i++) {
  35. [self drawMoney];
  36. }
  37. });
  38. }
  39. // 存钱
  40. - (void)saveMoney
  41. {
  42. pthread_mutex_lock(& _lock);
  43. int money = self.currMoney;
  44. sleep(.3);
  45. money += 100;
  46. self.currMoney = money;
  47. NSLog(@"存了100,还剩%d元", self.currMoney);
  48. pthread_mutex_unlock(& _lock);
  49. }
  50. // 取钱
  51. - (void)drawMoney
  52. {
  53. pthread_mutex_lock(& _lock);
  54. int money = self.currMoney;
  55. sleep(.3);
  56. money -= 50;
  57. self.currMoney = money;
  58. NSLog(@"取了50,还剩%d元", self.currMoney);
  59. pthread_mutex_unlock(& _lock);
  60. }

pthread_mutex – 递归锁

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

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

方法嵌套

  1. - (void)test1
  2. {
  3. pthread_mutex_lock(&_lock);
  4. NSLog(@"任务1");
  5. [self test2];
  6. pthread_mutex_unlock(&_lock);
  7. }
  8. - (void)test2
  9. {
  10. pthread_mutex_lock(&_lock);
  11. NSLog(@"任务2");
  12. pthread_mutex_unlock(&_lock);
  13. }

递归

  1. - (void)test1
  2. {
  3. pthread_mutex_lock(&_lock);
  4. NSLog(@"任务1");
  5. [self test1];
  6. pthread_mutex_unlock(&_lock);
  7. }

pthread_mutex – 条件锁

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

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

这是就需要用pthread_mutex条件锁。

实例代码

  1. #import "ViewController.h"
  2. #import <pthread.h>
  3. @interface ViewController ()
  4. @property (nonatomic, assign) pthread_mutex_t lock;
  5. @property (nonatomic, assign) pthread_cond_t condition;
  6. @property (nonatomic, strong) NSMutableArray *array;
  7. @end
  8. @implementation ViewController
  9. - (void)viewDidLoad {
  10. [super viewDidLoad];
  11. self.array = [NSMutableArray array];
  12. // 传入NUll 使用PTHREAD_MUTEX_DEFAULT类型的锁
  13. pthread_mutex_init(&_lock, NULL);
  14. pthread_cond_init(&_condition, NULL);
  15. dispatch_async(dispatch_get_global_queue(0, 0), ^{
  16. [self remove];
  17. });
  18. dispatch_async(dispatch_get_global_queue(0, 0), ^{
  19. [self add];
  20. });
  21. }
  22. // 删除数组中的元素
  23. - (void)remove
  24. {
  25. pthread_mutex_lock(&_lock);
  26. if (self.array.count == 0) {
  27. // 执行wait 当前线层进入休眠,放开mutex锁,被唤醒后对mutex再次加锁
  28. pthread_cond_wait(&_condition, &_lock);
  29. }
  30. NSLog(@"删除了元素");
  31. [self.array removeLastObject];
  32. pthread_mutex_unlock(&_lock);
  33. }
  34. // 往数组添加元素
  35. - (void)add
  36. {
  37. pthread_mutex_lock(&_lock);
  38. [self.array addObject:@"test"];
  39. // 发送信号 激活等待该条件的线程
  40. pthread_cond_signal(&_condition);
  41. // 广播 激活所有等待该条件的线程
  42. // pthread_cond_broadcast(&_condition);
  43. NSLog(@"添加了元素");
  44. pthread_mutex_unlock(&_lock);
  45. }

NSLock NSRecursiveLock

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

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

NSCondition

NSCondition是对mutex和cond的封装。

实例代码

  1. #import "ViewController.h"
  2. @interface ViewController ()
  3. @property (nonatomic, strong) NSMutableArray *array;
  4. @property (nonatomic, strong) NSCondition *condition;
  5. @end
  6. @implementation ViewController
  7. - (void)viewDidLoad {
  8. [super viewDidLoad];
  9. self.array = [NSMutableArray array];
  10. self.condition = [[NSCondition alloc] init];
  11. dispatch_async(dispatch_get_global_queue(0, 0), ^{
  12. [self remove];
  13. });
  14. dispatch_async(dispatch_get_global_queue(0, 0), ^{
  15. [self add];
  16. });
  17. }
  18. // 删除数组中的元素
  19. - (void)remove
  20. {
  21. // pthread_mutex_lock(&_lock);
  22. [self.condition lock];
  23. if (self.array.count == 0) {
  24. // 执行wait 当前线层进入休眠,放开mutex锁,被唤醒后对mutex再次加锁
  25. [self.condition wait];
  26. }
  27. NSLog(@"删除了元素");
  28. [self.array removeLastObject];
  29. [self.condition unlock];
  30. // pthread_mutex_unlock(&_lock);
  31. }
  32. // 往数组添加元素
  33. - (void)add
  34. {
  35. // pthread_mutex_lock(&_lock);
  36. [self.condition lock];
  37. [self.array addObject:@"test"];
  38. // 发送信号 激活等待该条件的线程
  39. [self.condition signal];
  40. // 广播 激活所有等待该条件的线程
  41. [self.condition broadcast];
  42. NSLog(@"添加了元素");
  43. [self.condition unlock];
  44. // pthread_mutex_unlock(&_lock);
  45. }

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. NX二次开发-UFUN编辑图层类别名字UF_LAYER_edit_category_name

    NX11+VS2013 #include <uf.h> #include <uf_layer.h> UF_initialize(); //创建图层类别 UF_LAYER_cat ...

  2. Spring随笔-bean装配

    Spring提供了三种装配方式 1.XML文件进行显式装配 2.java中进行显示装配 3.自动化装配 1.自动化装配的两种实现方式 1.组件扫描:Spring会自动发现应用上下文中创建的bean 2 ...

  3. Centos6.5安装ruby2.2.3

    一.安装库 Yum install –y gcc* openssl* wget 二.安装ruby wget https://cache.ruby-lang.org/pub/ruby/2.2/ruby- ...

  4. Cortex-M3的异常/中断屏蔽寄存器组

    转自 1. Cortex-M3的异常/中断屏蔽寄存器组 注:只有在特权级下,才允许访问这3个寄存器. 名 字 功能描述 PRIMASK 只有单一比特的寄存器.置为1后,就关掉所有可屏蔽异常,只剩下NM ...

  5. <router-link :to="...">

    一.<router-link :to="..."> to里的值可以是一个字符串路径,或者一个描述地址的对象.例如: // 字符串<router-link to=& ...

  6. 20140309 C++ using 野指针 返回变量首地址

    1.C++中的using:http://blog.sina.com.cn/s/blog_61e904fd0100nuk3.html 使用using恢复.改变被继承类中的访问权限 2.野指针,没有指向的 ...

  7. 初识OpenCV-Python - 001

    主要用代码注释来初步学习OpenCV-Python 1. 图片初使用(结合matplotlib) import cv2from matplotlib import pyplot as plt #Loa ...

  8. java锁分析

    import java.util.concurrent.TimeUnit; class Phone//Phone.java ---> Phone.class Class.forName(); { ...

  9. 数组模拟stack

    package com.cxy.springdataredis.data; import java.util.Scanner; public class StackDemo { public stat ...

  10. EXE 和 SYS 信息交互

    操了,分发函数少发一个,让我白调了两个多小时.