iOS多线程之Pthread/NSthread

来源:互联网 发布:ubuntu下安装jdk 编辑:程序博客网 时间:2024/05/16 05:10

要学习多线程的知识首先要了解一些多线程的基本知识,什么是线程,进程,多线程原理,多线程优缺点等。下面是多线程的基础知识

多线程基础知识


进程

进程是指在系统中正在运⾏行的一个应用程序
每个进程之间是独⽴的,每个进程均运⾏在其专用且受保护的内存空间内

线程

一个进程要想执⾏任务,必须得有线程(每1个进程⾄少要有1条线程)
线程是进程的基本执⾏单元,一个进程(程序)的所有任务都在线程中执⾏

  • 进程负责开辟内存空间
  • 线程负责执行任务

线程的串⾏(排排坐,吃果果,你一个,我一个)
一个线程中任务的执⾏是串⾏(顺序执⾏)的
如果要在一个线程中执⾏多个任务,那么只能⼀个⼀个地按顺序执⾏行这些任务
也就是说,在同⼀时间内,一个线程只能执⾏一个任务

多线程


一个进程中可以开启多条线程,每条线程可以并发(同时)执⾏不同的任务
多线程技术可以提⾼高程序的执⾏行效率

多线程的原理

同⼀时间,CPU只能处理一条线程,只有一条线程在⼯作(执⾏)。多线程并发(同时)执⾏,其实是CPU快速地在多条线程之间调度(切换), 如果CPU调度线程的时间⾜够快,就造成了多线程并发执⾏的假象。如果线程非常多,CPU会在N多线程之间调度,CPU会累死,消耗⼤大量的CPU资源。

多线程的优点

● 能适当提高程序的执行效率,开启新应用能立刻执行,不需要等之前的运行完毕。
● 能适当提高资源利用率(CPU、内存利⽤用率)

多线程的缺点

● 开启线程需要占用一定的内存空间(默认情况下,每一条线程都占用512KB),如果开启大量的线程,会占用大量的内存空间,降低程序的性能
● 线程越多,CPU在调度线程上的开销就越大
● 程序设计更加复杂:比如线程之间的通信、多线程的数据共享

主线程


一个IOS程序运行后,默认会开启一条线程,称为“主线程”或者“UI”线程。
主线程的作用:主要是显示和刷新UI界面,处理UI的操作事件
主线程注意:不要将耗时的操作放到主线程中,因为耗时的会一直占用主线程,导致UI的滚动,拖拽,点击事件不能被处理,影响UI的流畅度,出现“卡顿”现象,导致用户体验不佳。所以一般将耗时操作放在子线程中进行

[NSThread currentThread]
可以在所有的多线程技术中使用
打印当前线程3

[NSThread currentThread]
通常用来在多线程开发中,判断是否在主线程
number == 1 说明是主线程
number != 1 说明是其他线程


IOS多线程方案

实例化:pthread

    // 创建线程    // C语言中类型的结尾通常 _t/Ref,而且不需要使用 *    pthread_t threadId;    NSString *str = @"Hello Pthread";    int result = pthread_create(&threadId, NULL, &demo, (__bridge void *)(str));    /* 在其他线程执行  demo 函数  返回int类型的数值     参数     1. 线程代号的地址     2. 线程的属性     3. 调用函数的指针     4. 传递给该函数的参数     返回值     - 如果是0,表示正确     - 如果是非0,表示错误码     */    if (result == 0) {        NSLog(@"OK");    } else {        NSLog(@"error %d", result);    }}void *demo(void *param) {    NSString *sss = (__bridge NSString *)(param);    NSLog(@"%@, %@", [NSThread currentThread], sss);    return NULL;}

void * 和 OC 中的 id 是等价的

id(*)(id)

  • 在 ARC 开发中,如果设计到和 C 语言中相同的数据类型进行转换时,需要使用 __bridge “桥接”
  • 在 MRC 开发中,不需要桥接

    在 OC 中,如果是 ARC 开发,编译器会在编译的时候,自动根据代码结构,添加 retain, release, autorelease
    ARC 只负责 OC 部分的代码,不负责 C 的代码,如果 C 语言的框架出现 retain/create/copy 字样的函数,都需要release

    关于桥接的添加,可以利用 Xcode 辅助实现!

实例化:NSthread

主要语句:

    // 创建线程,运行demo方法1NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(demo:) object:@"Thread"];    // 启动线程    [thread start];2、[NSThread detachNewThreadSelector:@selector(demo:) toTarget:self withObject:@"Detach"];3、[self performSelectorInBackground:@selector(demo:) withObject:@"background"];@implementation ViewController- (void)viewDidLoad {    [super viewDidLoad];}- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {    [self threadDemo3_1];}// NSThread 创建//=====================================================================- (void)threadDemo3_1 {    Person *p = [[Person alloc] init];    [p performSelectorInBackground:@selector(loadData) withObject:nil];}//============================- (void)threadDemo3 {    // 打印当前是在那个线程    NSLog(@"1--%@", [NSThread currentThread]);    // 是 NSObject 的一个分类方法,意味着所有的 NSObject 都可以使用此方法,在其他线程执行方法!    // 特点:没有thread字眼,一旦制定,就会立即在后台线程执行 selector 方法    // performSelectorInBackground 隐式的多线程方法    // 这种方法,在使用的时候更加灵活!    [self performSelectorInBackground:@selector(demo:) withObject:@"background"];    NSLog(@"2--%@", [NSThread currentThread]);}//=====================================================================- (void)threadDemo2 {    // 1    NSLog(@"1--%@", [NSThread currentThread]);    // detachNewThreadSelector 会立即在后台线程执行 selector 方法,不需要手动star    // detach => 分离一个子线程执行 demo: 方法    [NSThread detachNewThreadSelector:@selector(demo:) toTarget:self withObject:@"Detach"];    // 1/2?    NSLog(@"2--%@", [NSThread currentThread]);}//=====================================================================- (void)threadDemo1 {    NSLog(@"1-------");    // 实例化线程/加载 => alloc(分配内存) / init(初始化)    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(demo:) object:@"Thread"];    // 启动线程    [thread start];    // 在线程1  输出    NSLog(@"2------- %@", [NSThread currentThread]);}- (void)demo:(id)obj {    for (int i = 0; i<2; i++) {        NSLog(@"%@ %@", [NSThread currentThread], obj);    }}@end

NSthread的属性

线程的名字:在大的商业项目中,通常希望程序崩溃的时候,能够获取到程序准确执行所在的线程!
线程优先级:优先级只是保证 CPU 调度的可能性会高,一般不设置优先级

    NSThread *t1 = [[NSThread alloc] initWithTarget:self selector:@selector(demo) object:nil];    t1.name = @"Thread A"; // name 在大得项目中,能够准确获取程序执行所在的线程    // 优先级,是一个浮点数,从0~1.0,1.0表示优先级最高,默认优先级是0.5    t1.threadPriority = 0.1;    [t1 start];

线程的状态

新建:NSThread *t = [[NSThread alloc] initWithTarget:self selector:@selector(threadStatus) object:nil]; 放在可调度线程池中

就绪:[t start];放在可调度线程池中

运行:NSThread *t = [[NSThread alloc] initWithTarget:self selector:@selector(threadStatus) object:nil];

阻塞(睡一会儿):[NSThread sleepForTimeInterval:2.0];从可调度线程池中移出

[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:2.0]];
从现在开始睡多久

死亡:[NSThread exit] 一般不要在主线程里面玩这个东东,在子线程中玩的时候,先做一个判断。exit之前要释放对象,因为Arc会清楚OC对象,
但是C的对象要手动清除。

多线程的安全隐患

互斥锁 - 保证锁内的代码,同一时间,只有一条线程能够执行!
利用互斥锁 防止把同一张车票卖给两个人

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {    self.tickets = 20;    NSThread *t1 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTickets) object:nil];    t1.name = @"售票员 A";    [t1 start];    NSThread *t2 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTickets) object:nil];    t2.name = @"售票员 B";    [t2 start];}// MARK: - 卖票逻辑/** 在开发多线程程序时,按照以下步骤 1. 保证单个线程执行正常! 2. 每一个售票逻辑(窗口)应该把所有的票卖完 3. 添加线程 */- (void)saleTickets {    while (YES) {        // 模拟休眠        [NSThread sleepForTimeInterval:1.0];        // 注意,锁一定要是所有线程共享的对象,不能是对象里面的一个局部变量(上厕所自己兜里的锁)        // 如果代码中只有一个地方需要加锁,大多都使用 self        // 互斥锁 - 保证锁内的代码,同一时间,只有一条线程能够执行!        // 要锁住关键的操作,锁定范围越小,效率越高        @synchronized(self) { // self就是加锁的对象,不能局部变量,能够加锁任意的NSObject对象            if (self.tickets > 0) {                self.tickets--;                NSLog(@"剩余票数 %d %@", self.tickets, [NSThread currentThread]);            } else {                NSLog(@"没有票了 %@", [NSThread currentThread]);                break;            }        }    }}

原子属性

  • nonatomic: 非原子属性(多线程,高效)
  • atomic: 原子属性(线程安全),就是针对多线程设计的,是默认属性
    多个线程写入属性时,保证同一时间只有一个线程能够执行写入操作
    单(线程)写多(线程)读的一种多线程技术,同样有可能出现“脏数据 (错误数据)”,重新读一下。

实际上,原子属性内部也有一把锁,自旋锁

自旋锁 & 互斥锁

  • 共同点
    都能够保证同一时间,只有一条线程执行锁定范围的代码
  • 不同点

    • 互斥锁:如果发现有其他线程正在执行锁定的代码,线程会进入休眠状态,等待其他线程执行完毕,打开锁之后,线程会被唤醒
    • 自旋锁:如果发现有其他线程正在执行锁定的代码,线程会用死循环的方式,一直等待锁定代码执行完成!
  • 自旋锁更适合执行非常短的代码!无论什么锁,都是要付出代价的!多线程是追求效率,有锁安全,但是效率低

线程安全

在多个线程进行读写操作时,仍然能够保证数据正确!效率不高!

UI 线程,共同约定:所有更新 UI 的操作都在主线程上执行!
原因:几乎所有的 UIKit 都不是线程安全的!”考虑到效率,只能这样取舍”

日常开发中,使用锁的机会很少,多线程的目的,就是将耗时的操作放在后台!

0 0
原创粉丝点击