凡是跟UI相关的都是在主线程执行的
UIKit类库的线程都是不安全的 所以我们需要在主线程上更新UI 因此主线程又叫UI线程
多线程的核心思想 : 就是把耗时操作放在后台执行,避免耗时操作卡死UI
currentThread : 查看当前线程
NSLog(@"%@",[NSThread currentThread]);
同步和异步
同步和异步是任务 / 代码 执行的两种方式
同步--->多个任务按顺序依次执行,就是同步执行
异步--->多个任务同时执行,就是异步执行
pthread
参数1 : 子线程的ID / 标识
在C语言中,一般带`_t` / `_ref`标识数据类型
参数2 : 子线程的属性,一般传NULL
NULL : 表示空地址,一般在C语言使用;
nil : 表示空对象,一般在OC使用;
其实,NULLh和nil本质上没有半点儿区别
参数3 : 子线程需要执行的函数
void *(*)(void *) : 表示指向函数的指针,即函数名;函数名就是表示函数地址;
数组地址就是数组名或者数组第0个角标元素的地址
void * 表示可以指向任何地址的指针,代表任意数据类型;类似于OC的id;
返回值 函数名 函数参数
void * (*) (void *)
参数4 : 传入到子线程需要执行的函数的参数
返回值 : int;在很多C语言框架中,并不是遵守非零既真;因为成功的结果只有一个,0是唯一的;失败的原因有很多;
线程调试 : 查看方法执行的线程是否是主线程或者子线程
{number = 1, name = main} : 表示主线程
{number = 3, name = (null)} : 只要number != 1 就表示子线程
提示 : 千万不要纠结number到底等于几,系统分配的
// 参数1
pthread_t ID;
// 创建了一个子线程,执行demo函数
int result = pthread_create(&ID, NULL, demo, NULL);
// 判断创建子线程是否成功
if (result == 0) {
NSLog(@"创建子线程成功");
} else {
NSLog(@"创建子线程失败");
}
子线程执行的函数
*/
void *demo(void *param)
{
NSString *str = (__bridge NSString *)(param);
// currentThread : 查看当前线程
NSLog(@"demo %@ - %@",[NSThread currentThread],str);
return NULL;
}
NSThread创建线程三种方式
[self performSelectorInBackground:@selector(demo:) withObject:@"perform"];
以上参数介绍:
1.NSObject分类方法创建子线程
2.不可以拿到线程对象
3.不需要手动启动线程
[NSThread detachNewThreadSelector:@selector(demo:) toTarget:self withObject:@"detach"];
以上参数介绍:
1.类方法创建子线程
2.不可以拿到线程对象
3.不需要手动启动线程
// 创建线程对象
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(demo:) object:@"alloc"];
// 启动线程
[thread start];
以上参数介绍:
1.构造方法创建子线程
2.可以拿到线程对象
3.需要手动启动线程
通知主线程刷新UI
// waitUntilDone : 是否等待updateUI执行完,再执行后面的代码,一般传入NO
[self performSelectorOnMainThread:@selector(updateUI:) withObject:image waitUntilDone:NO];
关于以上使用调用方法中-target和selector关系
@selector中需要传入一个方法
而target中需要传入对象告诉系统 这个方法是哪个对象的
线程的声明周期
提示 : 程序员只能做新建和就绪,其他的都由系统来处理
// 创建线程对象 : 新建状态
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(demo) object:nil];
// 启动线程 : 就绪状态(把线程对象添加到可调度线程池,等待被CPU调度执行)
[thread start];
// sleepForTimeInterval : 使当前线程休眠到指定时长
[NSThread sleepForTimeInterval:1.0];
// sleepUntilDate : 使当前线程休眠到指定日期
[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:2.0]];
// 使当前线程强制死亡
[NSThread exit];
线程的属性
//新建状态
NSThread *thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(demo) object:nil];
// 设置线程对象的name属性 : 标识一个唯一的线程对象,方便跟踪
thread1.name = @"t1";
thread1.threadPriority = 1.0;
// 设置线程对象的优先级 : 浮点数;范围是0.0~1.0;最高是1.0;默认是0.5
// 线程优先级不能决定线程执行的先后顺序,只能决定某个线程有更多的机会被CPU先调度执行完,概率事件
// 注意 : 实际开发中,千万不要设置优先级或者服务器质量,会出现意想不到的问题;使用默认的,让系统自己来处理
// threadPriority : 在目前即将被废弃,使用qualityOfService来替代;
thread1.qualityOfService = NSQualityOfServiceUserInteractive;
// stackSize : 线程占用内存空间大小
NSLog(@"子 %tu",[NSThread currentThread].stackSize / 1024);
多线程的资源共享问题
// 互斥锁 / 同步锁 : 使用了线程同步技术
// 特点 : 可以保证被锁定的代码,同一时间只有一个线程可以访问
// self : 表示互斥锁的参数;互斥锁的参数,又叫做锁对象;
// 锁对象 : 任何继承自NSObject的对象,都可以作为互斥锁的参数;内部有把锁,默认是开启的
// 锁对象必须是全局的对象;self是最方便获取的全局的锁对象
// 局部锁对象是锁不住的,因为每次线程进来之前会新建一把锁
// 提示 : 加锁的事情,不是再客户端操作的;是服务器加锁的,多线程资源共享绝大多数是在服务器发生;
// 提示 : 加锁是牺牲了性能,保证安全.客户端的性能不能轻易牺牲
@synchronized (self)
{
//需要被锁定的代码
}
参数必须传全局对象 多数传self
原子属性
使用nonatomic修饰的属性为非原子属性
使用atomic修饰的属性为原子属性
原子属性 : 单写多读
单写多读 : 同一时间只有一个线程可以访问setter方法;但是可以有多个线程访问getter方法
注意 : 原子属性的setter方法是线程安全的,getter方法是线程非安全的
setter方法内部有把自旋锁
自旋锁 :
看不见的,由系统封装的
可以保证被锁定的代码,同一时间只能有一个线程可以访问
一旦外面的线程,发现代码被自旋锁锁定,外面的线程会以死循环的方式等待开锁
互斥锁 :
可以保证被锁定的代码,同一时间只能有一个线程可以访问
一旦外面的线程,发现代码被互斥锁锁定,外面的线程会进入就绪状态,等待开锁
网友评论