文章目录
- 线程同步和线程安全
- 非线程安全(例子)
- NSThread
- GCD
- 线程安全
- NSThread
- GCD
线程同步和线程安全
线程安全:如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作(更改变量),一般都需要考虑线程同步,否则的话就可能影响线程安全。
线程同步:可理解为线程 A 和 线程 B 一块配合,A 执行到一定程度时要依靠线程 B 的某个结果,于是停下来,示意 B 运行;B 依言执行,再将结果给 A;A 再继续操作。
非线程安全(例子)
总共有100张火车票,有两个售卖火车票的窗口,一个是北京火车票售卖窗口,另一个是上海火车票售卖窗口。两个窗口同时售卖火车票,卖完为止。
NSThread
/**
* 初始化火车票数量、卖票窗口(非线程安全)、并开始卖票
*/
- (void)initTicketSaling{
self.ticketSurplusCount = 100;
self.ticketSaleWindowBJ = [[NSThread alloc]initWithTarget:self selector:@selector(saleTicket) object:nil];
self.ticketSaleWindowBJ.name = @"北京火车票售票窗口";
self.ticketSaleWindowSH = [[NSThread alloc]initWithTarget:self selector:@selector(saleTicket) object:nil];
self.ticketSaleWindowSH.name = @"上海火车票售票窗口";
[self.ticketSaleWindowBJ start];
[self.ticketSaleWindowSH start];
}
/**
* 售卖火车票(非线程安全)
*/
- (void)saleTicket{
while (1) {
if (self.ticketSurplusCount > 0){
self.ticketSurplusCount --;
NSLog(@"%@", [NSString stringWithFormat:@"剩余票数:%ld 窗口:%@", self.ticketSurplusCount, [NSThread currentThread].name]);
[NSThread sleepForTimeInterval:0.2];
}else{
NSLog(@"所有火车票均已售完");
break;
}
}
}
通过观察打印结果,我们发现在不考虑线程安全的情况下,得到票数是错乱的,并且部分票出现了重复。
GCD
/**
* 初始化火车票数量、卖票窗口(非线程安全)、并开始卖票
*/
- (void)initTicketSaling{
self.ticketSurplusCount = 100;
//北京火车票售卖窗口
dispatch_queue_t queueBJ = dispatch_queue_create("com.jun.bj", DISPATCH_QUEUE_SERIAL);
//上海火车票售卖窗口
dispatch_queue_t queueSH = dispatch_queue_create("com.jun.sh", DISPATCH_QUEUE_SERIAL);
__weak typeof(self) weakSelf = self;
dispatch_async(queueBJ, ^{
[weakSelf saleTicket];
});
dispatch_async(queueSH, ^{
[weakSelf saleTicket];
});
}
/**
* 售卖火车票(非线程安全)
*/
- (void)saleTicket{
while(1){
if(self.ticketSurplusCount > 0){
self.ticketSurplusCount--;
NSLog(@"%@", [NSString stringWithFormat:@"剩余票数:%d 窗口:%@", self.ticketSurplusCount, [NSThread currentThread]]);
[NSThread sleepForTimeInterval:0.2];
}else{
NSLog(@"所有火车票均已售完");
break;
}
}
}
通过观察打印结果,我们发现在不考虑线程安全的情况下,得到票数是错乱的,并且部分票出现了重复。
线程安全
线程安全解决方案:可以给线程加锁,在一个线程执行该操作的时候,不允许其他线程进行操作。iOS 实现线程加锁有很多种方式。@synchronized、 NSLock、NSRecursiveLock、NSCondition、NSConditionLock、pthread_mutex、dispatch_semaphore、OSSpinLock、atomic(property) set/get等等各种方式。
以下代码采用@synchronized方式。
NSThread
/**
* 初始化火车票数量、卖票窗口(线程安全)、并开始卖票
*/
- (void)initTicketSaling{
self.ticketSurplusCount = 100;
self.ticketSaleWindowBJ = [[NSThread alloc]initWithTarget:self selector:@selector(saleTicket) object:nil];
self.ticketSaleWindowBJ.name = @"北京火车票售票窗口";
self.ticketSaleWindowSH = [[NSThread alloc]initWithTarget:self selector:@selector(saleTicket) object:nil];
self.ticketSaleWindowSH.name = @"上海火车票售票窗口";
[self.ticketSaleWindowBJ start];
[self.ticketSaleWindowSH start];
}
/**
* 售卖火车票(线程安全)
*/
- (void)saleTicket{
while (1) {
// 互斥锁
@synchronized (self) {
if(self.ticketSurplusCount > 0){
self.ticketSurplusCount --;
NSLog(@"%@", [NSString stringWithFormat:@"剩余票数:%ld 窗口:%@", self.ticketSurplusCount, [NSThread currentThread].name]);
[NSThread sleepForTimeInterval:0.2];
}else{
NSLog(@"所有火车票均已售完");
break;
}
}
}
}
GCD
以下代码采用dispatch_semaphore方式。
/**
* 线程安全:使用 semaphore 加锁
* 初始化火车票数量、卖票窗口(线程安全)、并开始卖票
*/
- (void)initTicketSaling{
self.semaphoreLock = dispatch_semaphore_create(1);
self.ticketSurplusCount = 100;
//北京火车票售卖窗口
dispatch_queue_t queueBJ = dispatch_queue_create("com.jun.bj", DISPATCH_QUEUE_SERIAL);
//上海火车票售卖窗口
dispatch_queue_t queueSH = dispatch_queue_create("com.jun.sh", DISPATCH_QUEUE_SERIAL);
__weak typeof(self) weakSelf = self;
dispatch_async(queueBJ, ^{
[weakSelf saleTicket];
});
dispatch_async(queueSH, ^{
[weakSelf saleTicket];
});
}
/**
* 售卖火车票(线程安全)
*/
- (void)saleTicket{
while (1) {
dispatch_semaphore_wait(self.semaphoreLock, DISPATCH_TIME_FOREVER);
if(self.ticketSurplusCount > 0){
self.ticketSurplusCount--;
NSLog(@"%@", [NSString stringWithFormat:@"剩余票数:%d 窗口:%@", self.ticketSurplusCount, [NSThread currentThread]]);
[NSThread sleepForTimeInterval:0.2];
}else{
NSLog(@"所有火车票均已售完");
dispatch_semaphore_signal(self.semaphoreLock);
break;
}
dispatch_semaphore_signal(self.semaphoreLock);
}
}