1.进程和线程
- 线程是进程的基本执行单元,一个进程的所有任务都在线程中执行 * 进程要想执行任务,必须得有线程,进程至少要有一条线程
- 程序启动会默认开启一条线程,这条线程被称为主线程或 UI 线程
- 进程是指在系统中正在运行的一个应用程序
- 每个进程之间是独立的,每个进程均运行在其专用的且受保护的内存空间内
- 通过“活动监视器”可以查看 Mac 系统中所开启的进程
地址空间:同一进程的线程共享本进程的地址空间,而进程之间则是独立的地址空间。 资源拥有:同一进程内的线程共享本进程的资源如内存、I/O、cpu等,但是进程之间的 资源是独立的。
1: 一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃整个进 程都死掉。所以多进程要比多线程健壮。
2: 进程切换时,消耗的资源大,效率高。所以涉及到频繁的切换时,使用线程要好于进 程。同样如果要求同时进行并且又要共享某些变量的并发操作,只能用线程不能用进程 3: 执行过程:每个独立的进程有一个程序运行的入口、顺序执行序列和程序入口。但是 线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
4: 线程是处理器调度的基本单位,但是进程不是。 5: 线程没有地址空间,线程包含在进程地址空间中
- 优点
- 能适当提高程序的执行效率
- 能适当提高资源的利用率(CPU,内存)
- 线程上的任务执行完成后,线程会自动销毁
- 缺点
- 开启线程需要占用一定的内存空间(默认情况下,每一个线程都占 512 KB)
- 如果开启大量的线程,会占用大量的内存空间,降低程序的性能
- 线程越多,CPU 在调用线程上的开销就越大
- 程序设计更加复杂,比如线程间的通信、多线程的数据共享
/**
pthread_create 创建线程
参数:
1. pthread_t:要创建线程的结构体指针,通常开发的时候,如果遇到 C 语言的结构体,类型后缀 `_t / Ref` 结尾
同时不需要 `*`
2. 线程的属性,nil(空对象 - OC 使用的) / NULL(空地址,0 C 使用的)
3. 线程要执行的`函数地址`
void *: 返回类型,表示指向任意对象的指针,和 OC 中的 id 类似
(*): 函数名
(void *): 参数类型,void *
4. 传递给第三个参数(函数)的`参数`
返回值:C 语言框架中非常常见
int
0 创建线程成功!成功只有一种可能
非 0 创建线程失败的错误码,失败有多种可能!
*/
// 1: pthread
pthread_t threadId = NULL;
//c字符串
char *cString = "HelloCode";
// NSString *ocString = @"Gavin";
//延伸到: OC--C的混编 尤其在智能家居,SDK封装
//抛出一个问题: 在ARC需要这样操作,在MRC不需要
// OC prethread -- 跨平台
// 锁
int result = pthread_create(&threadId, NULL, pthreadTest, cString);
if (result == 0) {
NSLog(@"成功");
} else {
NSLog(@"失败");
}
// 2: NSThread
[NSThread detachNewThreadSelector:@selector(threadTest) toTarget:self withObject:nil];
// 3: GCD
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self threadTest];
});
// 4: NSOperation
[[[NSOperationQueue alloc] init] addOperationWithBlock:^{
[self threadTest];
}];
/**
1. 循环的执行速度很快
2. 栈区/常量区的内存操作也挺快
3. 堆区的内存操作有点慢
4. I(Input输入) / O(Output 输出) 操作的速度是最慢的!
* 会严重的造成界面的卡顿,影响用户体验!
* 多线程:开启一条线程,将耗时的操作放在新的线程中执行
*/
- (void)threadTest{
NSLog(@"begin");
NSInteger count = 1000 * 100;
for (NSInteger i = 0; i < count; i++) {
// 栈区
NSInteger num = i;
// 常量区
NSString *name = @"zhang";
// 堆区
NSString *myName = [NSString stringWithFormat:@"%@ - %zd", name, num];
NSLog(@"%@", myName);
}
NSLog(@"over");
}
void *pthreadTest(void *para){
// 接 C 语言的字符串
// NSLog(@"===> %@ %s", [NSThread currentThread], para);
// __bridge 将 C 语言的类型桥接到 OC 的类型
NSString *name = (__bridge NSString *)(para);
NSLog(@"===>%@ %@", [NSThread currentThread], name);
return NULL;
}
单核CPU,在内存中的多线程是快速切换,用户无法感知到切换
时间片的概念:CPU在多个任务直接进行快速的切换,这个时间间隔就是时间片
- (单核CPU)同一时间,CPU 只能处理 1 个线程 * 换言之,同一时间只有 1 个线程在执行
- 多线程同时执行:
- 是 CPU 快速的在多个线程之间的切换
- CPU 调度线程的时间足够快,就造成了多线程的“同时”执行的效果
- 如果线程数非常多
- CPU 会在 N 个线程之间切换,消耗大量的 CPU 资源 * 每个线程被调度的次数会降低,线程的执行效率降低
多核CPU,才是多个线程同时执行
2.线程属性,命名name
3.线程生命周期
线程池
饱和策略
线程中的所有任务都能cancel掉吗?正在执行的任务无法cancel,正在执行的任务已经被调度上去了
4.threadPriority优先级qualityOfService,优先级越高表明执行速度越快,还与任务的复杂度有关
5.多任务在一条线程,会有资源抢夺的问题
-
1.买票问题加一把锁:@synchronized(self),同样可以加NSLock,信号量,栅栏函数,同步
互斥锁小结
保证锁内的代码,同一时间,只有一条线程能够执行!
互斥锁的锁定范围,应该尽量小,锁定范围越大,效率越差!
互斥锁参数,能够加锁的任意 NSObject 对象
注意:锁对象一定要保证所有的线程都能够访问
如果代码中只有一个地方需要加锁,大多都使用 self,这样可以避免单独再建一个锁对象 -
2.atomic和nonatomic
nonatomic 非原子属性
atomic 原子属性(线程安全),针对多线程设计的,默认值
保证同一时间只有一个线程能够写入(但是同一个时间多个线程都可以取值) atomic 本身就有一把锁(自旋锁) 单写多读:单个线程写入,多个线程可以读取
atomic:线程安全,需要消耗大量的资源 nonatomic:非线程安全,适合内存小的移动设备
iOS 开发的建议
所有属性都声明为 nonatomic
尽量避免多线程抢夺同一块资源 尽量将加锁、资源抢夺的业务逻辑交给服务器端处理,减小移动客户端的压力
- 3.自旋锁和互斥锁
互斥锁:发现其他线程执行,当前线程 休眠(就绪状态) 一直在等打开 唤醒执行
自旋锁:发现其他线程执行,当前线程 询问 - 忙等 耗费性能比较高
短小用自旋锁
任务复杂度比较高用互斥锁
6.线程间通讯
- (void)viewDidLoad {
[super viewDidLoad];
_imageView = [[UIImageView alloc] init];
// 1. 准备 URL
NSString *urlString = @"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1529488045873&di=7c1cc40bec638406ef48717386a08094&imgtype=0&src=http%3A%2F%2Fpic2.ooopic.com%2F12%2F46%2F41%2F95bOOOPIC74_1024.jpg";
NSURL *url = [NSURL URLWithString:urlString];
// 2. 下载图像
//[self downloadImageWithURL:url];
// 异步下载图像
[self performSelectorInBackground:@selector(downloadImageWithURL:) withObject:url];
}
/**
* 下载 URL 指定的网络图片
*/
- (void)downloadImageWithURL:(NSURL *)url {
NSLog(@"%@", [NSThread currentThread]);
// 1. 所有从网络返回的都是二进制数据
NSData *data = [NSData dataWithContentsOfURL:url];
// 2. 将二进制数据转换成 image
UIImage *image = [UIImage imageWithData:data];
// 3. 在主线程更新 UI
// waitUntilDone: 是否等待 updateImage: 执行完成
[self performSelectorOnMainThread:@selector(updateImage:) withObject:image waitUntilDone:YES];
NSLog(@"完成");
}
/**
* 更新图像 UI
*/
- (void)updateImage:(UIImage *)image {
NSLog(@"更新 UI -> %@", [NSThread currentThread]);
// 3. 设置图像
_imageView.image = image;
// 4. 调整大小
[_imageView sizeToFit];
// 5. 设置 contentSize
_scrollView.contentSize = image.size;
}
7.端口间通信
把port加入runloop,[[NSRunLoop currentRunLoop] addPort:self.myPort forMode:NSDefaultRunLoopMode],接收port消息的端口加入runloop里面
8.UIImageView用weak修饰
weak修饰ImageView,一创建就释放了,栈区的空间系统自动管理回收,无法进行添加,可以通过添加中间变量进行赋值,引用计数进行传递,在函数viewDidLoad中都不会被销毁