文集
iOS开发之多线程(1)—— 概述
iOS开发之多线程(2)—— Thread
iOS开发之多线程(3)—— GCD
iOS开发之多线程(4)—— Operation
iOS开发之多线程(5)—— Pthreads
iOS开发之多线程(6)—— 线程安全与各种锁
API介绍
1. 创建
/**
thread: 线程ID
attr: 线程属性, 一般为NULL
start_routine: 新线程入口函数
arg: 入口函数start_routine的参数 (例如使用C++编程时的this指针)
返回值int: 创建成功返回0, 失败返回错误码
*/
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(start_routine)(void*), void *arg);
2. 终止
线程终止有一下几种方法:
- 从主函数返回 (调用return之后);
- 自己调用pthread_exit();
- 其他线程调用pthread_cancel();
- 进程中任意地方调用exit()导致进程结束.
3. 线程的分离与合并 (资源管理)
pthread有两种状态:
- joinable状态 (默认状态)
- unjoinable状态
我们创建一个线程时, pthread_create()里有个pthread_attr_t *attr属性, 其中有一个分离属性 (另外还有绑定属性, 调度属性等), 我们一般设置这个属性为null, 即使用默认属性, 此时pthread的状态是joinable状态. joinable顾名思义为可合并的, unjoinable则为不可合并的.
所谓合并是指在线程A中等待线程B结束, 称B合并于A.
什么时候线程可合并什么时候又不可合并呢?
这就要和线程的资源管理有关了. 线程属于系统资源 (如与之相似的内存资源), 有创建就要有销毁回收, 不然会有资源泄漏 (如与之相似的内存泄漏).
回收资源有两种方式:
- 系统自动回收
- 程序员编写代码主动回收
默认状态(joinable)下, 资源是由程序员主动回收的 (使用pthread_join()方法); 而unjoinable状态是系统自动回收的, 程序员不能手动合并 (回收资源).
怎样切换至unjoinable状态, 让系统自动回收资源呢?
可以在创建的时候设置好分离属性; 或者在使用默认属性 (传null) 创建线程后调用pthread_detach()方法, 这时就变成unjoinable状态由系统自动回收资源了. 也可以说, 分离是将资源的管理交由系统来处理.
综上, 线程的分离与合并有如下方法(API):
// 分离线程, 由系统自动管理资源, 之后不需再调用pthread_join()
int pthread_detach(pthread_t);
// 合并线程, 即手动管理(回收)资源
int pthread_join(pthread_t , void * _Nullable * _Nullable)
对了, 还要注意, pthread_join()会阻塞当前线程, 等到要合并的线程结束时, 才继续往下执行.
示例1
#import <pthread.h>
pthread_t m_threadID; // 线程ID (全局变量)
- (void)viewDidLoad {
[super viewDidLoad];
// 创建线程
[self createPthread];
sleep(1);
// 手动取消线程
[self cancelThread];
}
// 创建线程
- (void)createPthread {
/**
参数1: 线程ID
参数2: 线程属性, 一般为NULL
参数3: 新线程入口函数
参数4: 入口函数的参数
*/
int ret = pthread_create(&m_threadID, NULL, myThread, NULL);
if (ret != 0) {
NSLog(@"!!! 创建失败 err:%d", ret);
return;
}
// 分离线程 (自动管理资源, 后面不需调用pthread_join()来回收)
pthread_detach(m_threadID);
}
// 线程入口函数
void *myThread(void *param)
{
NSLog(@"1, %s, thread:%@", __func__, [NSThread currentThread]);
sleep(3);
NSLog(@"2, %s, thread:%@", __func__, [NSThread currentThread]);
// pthread_exit(NULL); // 这里作用与return差不多
return NULL;
}
// 取消线程
- (void)cancelThread {
pthread_cancel(m_threadID); // 取消线程
// pthread_join(m_threadID, NULL); // 如已分离线程则不需此步骤
}
本例中, 由于主线程在创建子线程1秒后马上取消了子线程, 所以只会打印log1而不会执行到log2.
log:
1, myThread, thread:<NSThread: 0x600000a01a80>{number = 6, name = (null)}
线程同步
线程的同步机制有很多种: 互斥锁, 事件, 信号量等等.
这里我们仅讨论最简单的: 互斥锁.
锁的三种操作:
- 加锁pthread_mutex_lock()
- 解锁pthread_mutex_unlock()
- 尝试加锁pthread_mutex_trylock()
一个线程加锁成功, 就会独自享有锁里面的线程资源, 其他线程无法访问, 直至解锁. 但如果获取失败(比如别的线程已经获取过了), 这时获取失败的线程就会被挂起, 直至锁被释放(解锁)后, 才能恢复运行. 如果我们不想等待, 就可以使用尝试加锁pthread_mutex_trylock(), 和pthread_mutex_lock()唯一不同的是, 尝试加锁失败了线程不会被挂起, 而是由我们决定线程继续等待还是做其他任务.
示例2
pthread_mutex_t m_mutex; // 互斥锁
int m_count; // 测试数
// 同步两个线程
- (void)syncPthread {
pthread_mutex_init(&m_mutex, NULL); // 创建锁
pthread_t pth1,pth2;
pthread_create(&pth1, NULL, thread1, NULL); // 创建线程1
pthread_create(&pth2, NULL, thread2, NULL); // 创建线程2
pthread_join(pth1, NULL); // 等待回收线程1
pthread_join(pth2, NULL); // 等待回收线程2
pthread_mutex_destroy(&m_mutex); // 销毁锁
}
void *thread1(void *arg)
{
for (int i=0; i<10; i++)
{
pthread_mutex_lock(&m_mutex);
NSLog(@"%s, count=%d", __func__, m_count);
m_count++;
pthread_mutex_unlock(&m_mutex);
sleep(1);
}
NSLog(@"%s, end", __func__);
return NULL;
}
void* thread2(void *arg)
{
for (int i=0; i<10; i++)
{
pthread_mutex_lock(&m_mutex);
NSLog(@"%s, count=%d", __func__, m_count);
m_count++;
pthread_mutex_unlock(&m_mutex);
sleep(2);
}
NSLog(@"%s, end", __func__);
return NULL;
}
可以看到, 虽然两个线程交替执行, 但是由于互斥锁不会争夺资源, m_count都是按顺序+1.
log:
thread2, count=0
thread1, count=1
thread1, count=2
thread1, count=3
thread2, count=4
thread1, count=5
thread1, count=6
thread2, count=7
thread1, count=8
thread1, count=9
thread2, count=10
thread1, count=11
thread1, count=12
thread2, count=13
thread1, count=14
thread1, end
thread2, count=15
thread2, count=16
thread2, count=17
thread2, count=18
thread2, count=19
thread2, end