多线程,做为实现软件并发执行的一个重要的方法,也开始具备愈来愈重要的地位!ios
正式由于多线程可以在时间片里被CPU快速切换,造就了如下优点算法
可是并非很是完美,由于多线程经常伴有资源抢夺的问题,做为一个高级开发人员并发编程那是必需要的,同时解决线程安全也成了咱们必需要要掌握的基础编程
自旋锁其实就是封装了一个spinlock_t
自旋锁安全
自旋锁:若是共享数据已经有其余线程加锁了,线程会以死循环的方式等待锁,一旦被访问的资源被解锁,则等待资源的线程会当即执行。自旋锁下面还会展开来介绍bash
互斥锁:若是共享数据已经有其余线程加锁了,线程会进入休眠状态等待锁。一旦被访问的资源被解锁,则等待资源的线程会被唤醒。数据结构
给你们 介绍一个群:763164022 此群不开车,天天只聊技术。 在群里的不少的开发者会写文互相交流,也会帮忙解决平时工做上的技术难点。还有多年的工做心得,但愿进群的群友遵照,进群不吹水,不开车的原则,共同创造一个良好的交流氛围。多线程
下面是自旋锁的实现原理:并发
bool lock = false; // 一开始没有锁上,任何线程均可以申请锁
do {
while(test_and_set(&lock); // test_and_set 是一个原子操做
Critical section // 临界区
lock = false; // 至关于释放锁,这样别的线程能够进入临界区
Reminder section // 不须要锁保护的代码
}
复制代码
这里有一篇关于原子性的比较有意思的文章,这里也贴出来,你们能够一块儿交流讨论 为何说atomic有时候没法保证线程安全呢? 再也不安全的 OSSpinLockasync
操做在底层会被编译为汇编代码以后不止一条指令,所以在执行的时候可能执行了一半就被调度系统打断,去执行别的代码,而咱们的原子性的单条指令的执行是不会被打断的,因此保证了安全.函数
尽管原子操做很是的简单,可是它只适合于比较简单特定的场合。在复杂的场合下,好比咱们要保证一个复杂的数据结构更改的原子性,原子操做指令就力不从心了,
若是临界区的执行时间过长,使用自旋锁不是个好主意。以前咱们介绍过期间片轮转算法,线程在多种状况下会退出本身的时间片。其中一种是用完了时间片的时间,被操做系统强制抢占。除此之外,当线程进行 I/O 操做,或进入睡眠状态时,都会主动让出时间片。显然在 while 循环中,线程处于忙等状态,白白浪费 CPU 时间,最终由于超时被操做系统抢占时间片。若是临界区执行时间较长,好比是文件读写,这种忙等是毫无必要的
下面开始咱们又爱又恨的锁
你们也能够参考这篇文章进行拓展:iOS锁
锁并是一种非强制机制,每个现货出呢个在访问数据或资源以前视图**获取(Acquire)锁,并在访问结束以后释放(Release)**锁。在锁已经被占用的时候试图获取锁,线程会等待,知道锁从新可用!
**二元信号量(Binary Semaphore)**只有两种状态:占用与非占用。它适合被惟一一个线程独占访问的资源。当二元信号量处于非占用状态时,第一个试图获取该二元信号量的线程会得到该锁,并将二元信号量置为占用状态,伺候其余的全部试图获取该二元信号量的线程将会等待,直到该锁被释放
如今咱们在这个基础上,咱们把学习的思惟由二元->多元的时候,咱们的信号量由此诞生,多元信号量简称信号量
将信号量的值减1
若是信号量的值小于0,则进入等待状态,不然继续执行。访问玩资源以后,线程释放信号量,进行以下操做
将信号量的值加1
若是信号量的值小于1,唤醒一个等待中的线程
let sem = DispatchSemaphore(value: 1)
for index in 1...5 {
DispatchQueue.global().async {
sem.wait()
print(index,Thread.current)
sem.signal()
}
}
输出结果:
1 <NSThread: 0x600003fa8200>{number = 3, name = (null)}
2 <NSThread: 0x600003f90140>{number = 4, name = (null)}
3 <NSThread: 0x600003f94200>{number = 5, name = (null)}
4 <NSThread: 0x600003fa0940>{number = 6, name = (null)}
5 <NSThread: 0x600003f94240>{number = 7, name = (null)}
复制代码
互斥量(Mutex)又叫互斥锁和二元信号量很相似,但和信号量不一样的是,信号量在整个系统能够被任意线程获取并释放;也就是说哪一个线程锁的,要哪一个线程释放锁。
具体详细的用法能够参考:常见锁用法
Mutex
能够分为递归锁(recursive mutex)
和非递归锁(non-recursive mutex)
。 递归锁也叫可重入锁(reentrant mutex)
,非递归锁也叫不可重入锁(non-reentrant mutex)
。 两者惟一的区别是:
NSLock
是最简单额互斥锁!可是是非递归的!直接封装了pthread_mutex
用法很是简单就不作赘述 @synchronized
是咱们互斥锁里面用的最频繁的,可是性能最差!
int main(int argc, const char * argv[]) {
NSString *obj = @"Iceberg";
@synchronized(obj) {
NSLog(@"Hello,world! => %@" , obj);
}
}
复制代码
底层clang
int main(int argc, const char * argv[]) {
NSString *obj = (NSString *)&__NSConstantStringImpl__var_folders_8l_rsj0hqpj42b9jsw81mc3xv_40000gn_T_block_main_54f70c_mi_0;
{
id _rethrow = 0;
id _sync_obj = (id)obj;
objc_sync_enter(_sync_obj);
try {
struct _SYNC_EXIT {
_SYNC_EXIT(id arg) : sync_exit(arg) {}
~_SYNC_EXIT() {
objc_sync_exit(sync_exit);
}
id sync_exit;
} _sync_exit(_sync_obj);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_8l_rsj0hqpj42b9jsw81mc3xv_40000gn_T_block_main_54f70c_mi_1 , obj);
} catch (id e) {
_rethrow = e;
}
{
struct _FIN {
_FIN(id reth) : rethrow(reth) {}
~_FIN() {
if (rethrow)
objc_exception_throw(rethrow);
}
id rethrow;
} _fin_force_rethow(_rethrow);
}
}
}
复制代码
咱们发现
objc_sync_enter
函数是在try
语句以前调用,参数为须要加锁的对象。由于C++
中没有try{}catch{}finally{}
语句,因此不能在finally{}
调用objc_sync_exit
函数。所以objc_sync_exit
是在_SYNC_EXIT
结构体中的析构函数
中调用,参数一样是当前加锁的对象。这个设计很巧妙,缘由在_SYNC_EXIT
结构体类型的_sync_exit
是一个局部变量,生命周期为try{}
语句块,其中包含了@sychronized{}
代码须要执行的代码,在代码完成后,_sync_exit
局部变量出栈释放,随即调用其析构函数,进而调用objc_sync_exit
函数。即便try{}
语句块中的代码执行过程当中出现异常,跳转到catch{}
语句,局部变量_sync_exit
一样会被释放,完美的模拟了finally
的功能。
因为篇幅缘由,这里分享一篇很是不错的博客:底层分析synchronized
int objc_sync_enter(id obj)
{
int result = OBJC_SYNC_SUCCESS;
if (obj) {
SyncData* data = id2data(obj, ACQUIRE);
require_action_string(data != NULL, done, result = OBJC_SYNC_NOT_INITIALIZED, "id2data failed");
result = recursive_mutex_lock(&data->mutex);
require_noerr_string(result, done, "mutex_lock failed");
} else {
// @synchronized(nil) does nothing
if (DebugNilSync) {
_objc_inform("NIL SYNC DEBUG: @synchronized(nil); set a breakpoint on objc_sync_nil to debug");
}
objc_sync_nil();
}
done:
return result;
}
复制代码
从上面的源码中咱们能够得出你调用sychronized
的每一个对象,Objective-C runtime
都会为其分配一个递归锁并存储在哈希表中。完美
其实若是你们以为@sychronized
性能低的话,彻底能够用NSRecursiveLock
现成的封装好的递归锁
NSRecursiveLock *lock = [[NSRecursiveLock alloc] init];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
static void (^RecursiveBlock)(int);
RecursiveBlock = ^(int value) {
[lock lock];
if (value > 0) {
NSLog(@"value:%d", value);
RecursiveBlock(value - 1);
}
[lock unlock];
};
RecursiveBlock(2);
});
2016-08-19 14:43:12.327 ThreadLockControlDemo[1878:145003] value:2
2016-08-19 14:43:12.327 ThreadLockControlDemo[1878:145003] value:1
复制代码
条件变量(Condition Variable)
做为一种同步手段,做用相似一个栅栏。对于条件变量,现成能够有两种操做:
换句话说:使用条件变量可让许多线程一块儿等待某个时间的发生,当某个时间发生时,全部的线程能够一块儿恢复执行!
相信仔细的你们确定在锁的用法里面见过NSCondition
,就是封装了条件变量pthread_cond_t
和互斥锁
- (void) signal {
pthread_cond_signal(&_condition);
}
// 其实这个函数是经过宏来定义的,展开后就是这样
- (void) lock {
int err = pthread_mutex_lock(&_mutex);
}
复制代码
NSConditionLock
借助 NSCondition
来实现,它的本质就是一个生产者-消费者模型
。“条件被知足”能够理解为生产者提供了新的内容。NSConditionLock
的内部持有一个NSCondition
对象,以及 _condition_value
属性,在初始化时就会对这个属性进行赋值:
// 简化版代码
- (id) initWithCondition: (NSInteger)value {
if (nil != (self = [super init])) {
_condition = [NSCondition new]
_condition_value = value;
}
return self;
}
复制代码
比互斥量更加严格的同步手段。在术语中,把临界区的获取称为进入临界区,而把锁的释放称为离开临界区。与互斥量和信号量的区别:
// 临界区结构对象
CRITICAL_SECTION g_cs;
// 共享资源
char g_cArray[10];
UINT ThreadProc10(LPVOID pParam)
{
// 进入临界区
EnterCriticalSection(&g_cs);
// 对共享资源进行写入操做
for (int i = 0; i < 10; i++)
{
g_cArray[i] = a;
Sleep(1);
}
// 离开临界区
LeaveCriticalSection(&g_cs);
return 0;
}
UINT ThreadProc11(LPVOID pParam)
{
// 进入临界区
EnterCriticalSection(&g_cs);
// 对共享资源进行写入操做
for (int i = 0; i < 10; i++)
{
g_cArray[10 - i - 1] = b;
Sleep(1);
}
// 离开临界区
LeaveCriticalSection(&g_cs);
return 0;
}
……
void CSample08View::OnCriticalSection()
{
// 初始化临界区
InitializeCriticalSection(&g_cs);
// 启动线程
AfxBeginThread(ThreadProc10, NULL);
AfxBeginThread(ThreadProc11, NULL);
// 等待计算完毕
Sleep(300);
// 报告计算结果
CString sResult = CString(g_cArray);
AfxMessageBox(sResult);
}
复制代码
int pthread_rwlock_rdlock(pthread_rwlock_t *rwptr);
int pthread_rwlock_wrlock(pthread_rwlock_t *rwptr);
int pthread_rwlock_unlock(pthread_rwlock_t *rwptr);
复制代码
ReadWriteLock
管理一组锁,一个是只读的锁
,一个是写锁
。读锁能够在没有写锁的时候被多个线程同时持有,写锁是独占的。
#include <pthread.h> //多线程、读写锁所需头文件
pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER; //定义和初始化读写锁
写模式:
pthread_rwlock_wrlock(&rwlock); //加写锁
写写写……
pthread_rwlock_unlock(&rwlock); //解锁
读模式:
pthread_rwlock_rdlock(&rwlock); //加读锁
读读读……
pthread_rwlock_unlock(&rwlock); //解锁
复制代码
这里用条件变量+互斥锁来实现。注意:条件变量必须和互斥锁一块儿使用,等待、释放的时候都须要加锁。
#include <pthread.h> //多线程、互斥锁所需头文件
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; //定义和初始化互斥锁
pthread_cond_t cond = PTHREAD_COND_INITIALIZER; //定义和初始化条件变量
写模式:
pthread_mutex_lock(&mutex); //加锁
while(w != 0 || r > 0)
{
pthread_cond_wait(&cond, &mutex); //等待条件变量的成立
}
w = 1;
pthread_mutex_unlock(&mutex);
写写写……
pthread_mutex_lock(&mutex);
w = 0;
pthread_cond_broadcast(&cond); //唤醒其余因条件变量而产生的阻塞
pthread_mutex_unlock(&mutex); //解锁
读模式:
pthread_mutex_lock(&mutex);
while(w != 0)
{
pthread_cond_wait(&cond, &mutex); //等待条件变量的成立
}
r++;
pthread_mutex_unlock(&mutex);
读读读……
pthread_mutex_lock(&mutex);
r- -;
if(r == 0)
pthread_cond_broadcast(&cond); //唤醒其余因条件变量而产生的阻塞
pthread_mutex_unlock(&mutex); //解锁
复制代码
这里使用2个互斥锁+1个整型变量来实现
#include <pthread.h> //多线程、互斥锁所需头文件
pthread_mutex_t r_mutex = PTHREAD_MUTEX_INITIALIZER; //定义和初始化互斥锁
pthread_mutex_t w_mutex = PTHREAD_MUTEX_INITIALIZER;
int readers = 0; //记录读者的个数
写模式:
pthread_mutex_lock(&w_mutex);
写写写……
pthread_mutex_unlock(&w_mutex);
读模式:
pthread_mutex_lock(&r_mutex);
if(readers == 0)
pthread_mutex_lock(&w_mutex);
readers++;
pthread_mutex_unlock(&r_mutex);
读读读……
pthread_mutex_lock(&r_mutex);
readers- -;
if(reader == 0)
pthread_mutex_unlock(&w_mutex);
pthread_mutex_unlock(&r_mutex);
复制代码
这里使用2个信号量+1个整型变量来实现。令信号量的初始数值为1,那么信号量的做用就和互斥量等价了。
#include <semaphore.h> //线程信号量所需头文件
sem_t r_sem; //定义信号量
sem_init(&r_sem, 0, 1); //初始化信号量
sem_t w_sem; //定义信号量
sem_init(&w_sem, 0, 1); //初始化信号量
int readers = 0;
写模式:
sem_wait(&w_sem);
写写写……
sem_post(&w_sem);
读模式:
sem_wait(&r_sem);
if(readers == 0)
sem_wait(&w_sem);
readers++;
sem_post(&r_sem);
读读读……
sem_wait(&r_sem);
readers- -;
if(readers == 0)
sem_post(&w_sem);
sem_post(&r_sem);
复制代码
线程的安全是如今各个领域在多线程开发必需要掌握的基础!只有对底层有所掌握,才能在真正的实际开发中游刃有余!如今的iOS开发乃至其余开发都是表面基础层开发,真正大牛开发是必需要掌握的,这一篇博客以供你们一块儿学习!也给你们推荐一个群:763164022 此群不开车,天天只聊技术。在群里的不少的开发者会写文互相交流,也会帮忙解决平时工做上的技术难点。还有多年的工做心得,但愿进群的群友遵照,进群不吹水,不开车的原则,共同创造一个良好的交流氛围。