多线程
线程的定义:
线程是进程的基本执行单元,一个进程的所有任务都在线程中执行
进程要想执行任务,必须得有线程,进程至少要有一条线程
程序启动会默认开启一条线程,这条线程被称为主线程或UI线程
进程的定义 :
进程是指在系统中正在运行的一个应用程序
每个进程之间是独立的,每个进程均运行在其专用的且受保护的内存
进程与线程的关系:
地址空间:同一进程的线程共享本进程的地址空间,而进程之间则是独立的地址空间。
资源拥有:同一进程内的线程共享本进程的资源如内存、I/O、cpu等,但是进程之间的资源是独立的。
一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃整个进程都死掉。所以多进程要比多线程健壮。
进程切换时,消耗的资源大,效率高。所以涉及到频繁的切换时,使用线程要好于进程。同样如果要求同时进行并且又要共享某些变量的并发操作,只能用线程不能用进程
执行过程:每个独立的进程有一个程序运行的入口、顺序执行序列和程序入口。但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
线程是处理器调度的基本单位,但是进程不是。(cpu调度)
多线程的意义: 解决一些在主线程执行的耗时操作,从而提升用户体验
* 优点
* 能适当提高程序的执行效率
* 能适当提高资源的利用率(CPU,内存)
* 线程上的任务执行完成后,线程会自动销毁
* 缺点
* 开启线程需要占用一定的内存空间(默认情况下,每一个线程都占 512 KB)
* 如果开启大量的线程,会占用大量的内存空间,降低程序的性能
* 线程越多,CPU 在调用线程上的开销就越大
* 程序设计更加复杂,比如线程间的通信、多线程的数据共享
多线程的原理:cpu调度切换执行线程
创建线程的消耗
主线程开辟空间512k,文档截图那里有点问题。
NSThread:
pthread_t:
线程生命周期:
线程池:
线程池:
饱和策略相关内容参考:博客
线程安全: 两个线程同时操作一个属性对象会导致一张票卖两次的情况
使用 @synchronized(self){} 互斥锁(递归锁,非递归锁重复访问资源会崩溃)修饰后可以避免这种情况
锁还有很多种:
{
// 不再安全的 OSSpinLock 除此dispatch_semaphore 和 pthread_mutex 性能是最高的
OSSpinLock lock = OS_SPINLOCK_INIT;
begin = CACurrentMediaTime();
for (int i = 0; i < count; i++) {
OSSpinLockLock(&lock);
OSSpinLockUnlock(&lock);
}
end = CACurrentMediaTime();
TimeCosts[LockTypeOSSpinLock] += end - begin;
printf("OSSpinLock: %8.2f ms\n", (end - begin) * 1000);
}
{
// 同步 ---
// > 0
// GCD 控制并发数
dispatch_semaphore_t lock = dispatch_semaphore_create(2); 控制并发数,如果设置1就是同步执行,只允许1条线程运行,相当于避免了多线程资源抢夺
begin = CACurrentMediaTime();
for (int i = 0; i < count; i++) {
dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
dispatch_semaphore_signal(lock);
}
end = CACurrentMediaTime();
TimeCosts[LockTypedispatch_semaphore] += end - begin;
printf("dispatch_semaphore: %8.2f ms\n", (end - begin) * 1000);
}
{
pthread_mutex_t lock;
pthread_mutex_init(&lock, NULL);
begin = CACurrentMediaTime();
for (int i = 0; i < count; i++) {
pthread_mutex_lock(&lock);
pthread_mutex_unlock(&lock);
}
end = CACurrentMediaTime();
TimeCosts[LockTypepthread_mutex] += end - begin;
pthread_mutex_destroy(&lock);
printf("pthread_mutex: %8.2f ms\n", (end - begin) * 1000);
}
{条件锁
NSCondition *lock = [NSCondition new];
begin = CACurrentMediaTime();
for (int i = 0; i < count; i++) {
[lock lock];
[lock unlock];
}
end = CACurrentMediaTime();
TimeCosts[LockTypeNSCondition] += end - begin;
printf("NSCondition: %8.2f ms\n", (end - begin) * 1000);
}
{
NSLock *lock = [NSLock new];
begin = CACurrentMediaTime();
for (int i = 0; i < count; i++) {
[lock lock];
[lock unlock];
}
end = CACurrentMediaTime();
TimeCosts[LockTypeNSLock] += end - begin;
printf("NSLock: %8.2f ms\n", (end - begin) * 1000);
}
{
pthread_mutex_t lock;
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
pthread_mutex_init(&lock, &attr);
pthread_mutexattr_destroy(&attr);
begin = CACurrentMediaTime();
for (int i = 0; i < count; i++) {
pthread_mutex_lock(&lock);
pthread_mutex_unlock(&lock);
}
end = CACurrentMediaTime();
TimeCosts[LockTypepthread_mutex_recursive] += end - begin;
pthread_mutex_destroy(&lock);
printf("pthread_mutex(recursive): %8.2f ms\n", (end - begin) * 1000);
}
{
NSRecursiveLock *lock = [NSRecursiveLock new];
begin = CACurrentMediaTime();
for (int i = 0; i < count; i++) {
[lock lock];
[lock unlock];
}
end = CACurrentMediaTime();
TimeCosts[LockTypeNSRecursiveLock] += end - begin;
printf("NSRecursiveLock: %8.2f ms\n", (end - begin) * 1000);
}
{
NSConditionLock *lock = [[NSConditionLock alloc] initWithCondition:1];
begin = CACurrentMediaTime();
for (int i = 0; i < count; i++) {
[lock lock];
[lock unlock];
}
end = CACurrentMediaTime();
TimeCosts[LockTypeNSConditionLock] += end - begin;
printf("NSConditionLock: %8.2f ms\n", (end - begin) * 1000);
}
{
NSObject *lock = [NSObject new];
begin = CACurrentMediaTime();
for (int i = 0; i < count; i++) {
@synchronized(lock) {}
}
end = CACurrentMediaTime();
TimeCosts[LockTypesynchronized] += end - begin;
printf("@synchronized: %8.2f ms\n", (end - begin) * 1000);
}
各个锁的性能:
第一推荐使用 dispatch_semaphore 信号量
第二推荐 NSLock 轻便,用的最多的
第三推荐 @synchronized 语法简单,但是性能是做弱的
atomic 与 nonatomic 就是典型的自旋锁
自旋锁:忙等,一直询问是否有在执行的线程。
互斥锁:如果有正在执行的线程,就休眠等待。
读写锁:只允许一条线程写,但是允许多条线程读。
互斥锁:允许多条线程访问,但是写的话只允许一天线程写入
创建线程后,runloop不调用run是否不调用线程执行,runloop 常驻线程,可以保证线程不退出,runloop底层存储在一个全局字典中,dict[key(线程指针),runloop],线程的执行和runloop没有关系,runloop的执行是建立在线程上的,线程的start和调度和runloop没有关系,runloop无法对线程进行直接操作,runloop可以管理线程中执行的任务。
线程执行完成后会等一段时间,看是否有需要执行的任务,如果没有就会被线程池回收,然后销毁,或者占用。(一般线程)
timer的执行是依赖于runloop的,比较特殊。
自旋锁与互斥锁的区别
从实现原理上来讲,互斥锁属于sleep-waiting类型的锁。例如在一个双核的机器上有两个线程(线程A和线程B),它们分别运行在Core0和 Core1上。假设线程A想要通过pthread_mutex_lock操作去得到一个临界区的锁,而此时这个锁正被线程B所持有,那么线程A就会被阻塞 (blocking),Core0 会在此时进行上下文切换(Context Switch)将线程A置于等待队列中,此时Core0就可以运行其他的任务(例如另一个线程C)而不必进行忙等待。而Spin lock则不然,它属于busy-waiting类型的锁,如果线程A是使用pthread_spin_lock操作去请求锁,那么线程A就会一直在 Core0上进行忙等待并不停的进行锁请求,直到得到这个锁为止。
自旋锁与互斥锁有点类似,只是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是 否该自旋锁的保持者已经释放了锁。
总结:
自旋锁会忙等: 所谓忙等,即在访问被锁资源时,调用者线程不会休眠,而是不停循环在那里,直到被锁资源释放锁。
互斥锁会休眠: 所谓休眠,即在访问被锁资源时,调用者线程会休眠,此时cpu可以调度其他线程工作。直到被锁资源释放锁。此时会唤醒休眠线程。
优缺点:
自旋锁的优点在于,因为自旋锁不会引起调用者睡眠,所以不会进行线程调度,cpu时间片轮转等耗时操作。所有如果能在很短的时间内获得锁,自旋锁的效率远高于互斥锁。
缺点在于,自旋锁一直占用CPU,他在未获得锁的情况下,一直运行--自旋,所以占用着CPU,如果不能在很短的时 间内获得锁,这无疑会使CPU效率降低。自旋锁不能实现递归调用。
pthread_mutex 表示互斥锁。互斥锁可以传入不同参数,实现递归锁pthread_mutex(recursive)。NSLock,NSCondition,NSRecursiveLock,NSConditionLock都是内部封装的pthread_mutex,即都属于互斥锁。@synchronized是NSLock的一种封装,牺牲了效率,简洁了语法。
OSSpinLock 表示自旋锁,从上图可以看到自旋锁的效率最高,但是现在的iOS因为优先级反转的问题,已经不安全,所以推荐使用pthread_mutex或者dispatch_semaphore。