iOS装13之多线程.docx
- 文档编号:8343153
- 上传时间:2023-01-30
- 格式:DOCX
- 页数:16
- 大小:178.18KB
iOS装13之多线程.docx
《iOS装13之多线程.docx》由会员分享,可在线阅读,更多相关《iOS装13之多线程.docx(16页珍藏版)》请在冰豆网上搜索。
iOS装13之多线程
iOS装13-之多线程
全栈线程与进程
开始之前先问自己几个问题
1、大学用C语言写的main函数里只写了helloword,面有线程么
2、CPU频率和个数与多线程有什么关系
3、进程和线程的关系
4、线程的访问权限,它都可以访问哪些东西
5、线程同步都有哪几种锁
6、线程中都有哪些坑需要注意
大学用C语言写的main函数里只写了helloword,面有线程么
Inanon-concurrentapplication,thereisonlyonethreadofexecution.Thatthreadstartsandendswithyourapplication’smainroutineandbranchesone-by-onetodifferentmethodsorfunctionstoimplementtheapplication’soverallbehavior.
这是来自苹果官方文档,之所以要摘自文档,只想让答案更有说服力。
也就是即使只有一个main函数也是存在线程的。
CPU频率和个数与多线程有什么关系
目前CPU的频率已经达到了一个极限,很难在技术上有很高的突破,于是人们开始向多核发展。
当只有一个CPU的时候不存在真正意义上的多线程,只不过是CPU在各个线程不停的切换,切换的频率即CPU的频率。
多个CPU才是真正的多线程
进程和线程的关系
线程是CPU调度的基本单位,一个进程至少包含一个线程。
Linux内核中并不存在真正意义上的线程概念,所有的执行实体都称为任务(Task),每个人物概念上都类似于一个单线程的进程,具有内存空间、执行实体、文件资源,不过Linux下不同的人物之间可以选择共享内存空间。
因此共享了同一个内存空间的多个任务构成了一个进程,这些任务也就成了进程里的线程。
另外一个程序可能包含多个进程。
可以在mac里执行psaux|grepXcode查看Xcode运行了多个进程
线程的访问权限,它都可以访问哪些东西
每个线程都有自己的栈(尽管并非完全无法被其它线程访问,但是一般情况下仍然可以认为是私有的数据)、寄存器(执行流的基本数据,因此为线程私有)和线程局部存储(ThreadLocalStorage,TLS)。
因此从代码上来看,线程私有的有:
局部变量、函数的参数、TLS数据。
线程间共有的有:
全局变量、堆上的数据、函数里的静态变量、打开的文件(A线程打开的文件可以由B线程读写)
官方文档中TLS
>ConfiguringThread-LocalStorage>Eachthreadmaintainsadictionaryofkey-valuepairsthatcanbeaccessedfromanywhereinthethread.Youcanusethisdictionarytostoreinformationthatyouwanttopersistthroughouttheexecutionofyourthread.Forexample,youcoulduseittostorestateinformationthatyouwanttopersistthroughmultipleiterationsofyourthread’srunloop.>CocoaandPOSIXstorethethreaddictionaryindifferentways,soyoucannotmixandmatchcallstothetwotechnologies.Aslongasyoustickwithonetechnologyinsideyourthreadcode,however,theendresultsshouldbesimilar.InCocoa,youusethethreadDictionarymethodofanNSThreadobjecttoretrieveanNSMutableDictionaryobject,towhichyoucanaddanykeysrequiredbyyourthread.InPOSIX,youusethepthread_setspecificandpthread_getspecificfunctionstosetandgetthekeysandvaluesofyourthread.
线程同步都有哪几种锁
信号量(Semaphore)
信号量又分为二元信号量和多元信号量。
二元信号量是最简单的一种锁,它只有两种状态:
占用和非占用。
它适合只能被唯一一个线程独占访问的资源。
当二元信号量处于非占用的状态时,第一个试图获取该二元信号量的线程会获得该锁,并将二元信号量之为占用状态,此后其它所有试图获取该二元信号量的新城将会等待,直到该锁被释放。
多元信号量会有一个N值,获取信号量后N减一,如果N小于0则等待。
访问资源后线程释放信号量N加1,如果N小于1,唤醒下一个等待的线程
互斥量(Mutex)
互斥量和二元信号量相似,不同的是信号量在整个系统中可以被任意线程获取并释放,而互斥量则有求那个线程获取了互斥量,哪个线程就要负责释放这个锁,其它香橙想去释放是不行的。
临界区(CriticalSection)
临界区是比互斥量更加严格的同步手段,在术语中把临界区的锁的获取称为进入临界区,而把锁的释放称为离开临界区。
它与互斥量和信号量的区别在于,互斥量和信号量在系统的的任何进程都是可见的,也就是说一个进程创建了一个互斥量和信号量,另一个进程试图去获取该锁是合法的。
而临界区的作用范围仅限于本进程。
其它进程无法获取该锁。
除此之外,临界区具有和互斥量相同的性质。
读写锁(Read-WriteLock)
对于一段数据,多个线程读写频繁,但是仅仅偶尔写入,如果用以上几种锁就会非常低效,用读写锁更快一点。
读写锁的获取又分为共享的(Shared)或独占的(Exclusive),看字面意思也能猜个大概,就不解释了。
条件变量(ConditionVariable)
作用类似于栅栏,使用条件变量可以使许多线程一起等待某个事件的发生,当事件发生时(条件变量被唤醒),所有的线程可以一起恢复执行。
线程中都有哪些坑需要注意
线程安全比较难搞,因为你即使合理用了锁,也不一定能保证线程安全。
原因有三:
1、编译器会为了提高速度将一个变量缓存到寄存器中而不写回
2、编译器会在进行优化的时候为了效率交换毫不相干的两条相邻指令的执行顺序
3、早在几十年前,CPU就发展出了动态调度,为了执行程序的时候为了提高效率有可能交换指令的顺序
解决1用volatile,解决后两者用barrier
从AFNetworking看上面iOS里面的多线程
iOS各种多线程的介绍点我
我们主要看多线程相关所以请将AFNetworking切换到2.x分支,尽管NSURLConnection已经在iOS9.0停用了,but我们只关注多线程。
我们知道实现多线程主要有NSThread、POSIX(pthread)、NSOperation、GCD。
而NSThread、NSOperation、GCD都是由pthread实现,只不过它们的使用方法各有不同。
首先AFNetworking要面向所有的网络请求,应该有取消的功能,这时可以排出GCD,其次网络请求还有可能需要批量处理,或者请求之间有依赖、或者可以设置线程并发数,这时又可以排出NSThread,现在只剩下了NSOperation。
现在来看NSURLConnection文件夹下三个类AFHTTPRequestOperation、AFURLConnectionOperation、AFHTTPRequestOperationManager。
其中AFHTTPRequestOperation继承自AFURLConnectionOperation,而AFURLConnectionOperation又继承自NSOperation。
ok,现在看到想看的了,话说回来NSOperation要想实现并发需要做什么?
需要重载startisReadyisExecutingisFinishedisConcurrent,现在是上代码的时候了。
-(BOOL)isReady{
returnself.state==AFOperationReadyState&&[superisReady];
}
-(BOOL)isExecuting{
returnself.state==AFOperationExecutingState;
}
-(BOOL)isFinished{
returnself.state==AFOperationFinishedState;
}
-(BOOL)isConcurrent{
returnYES;
}
-(void)start{
[self.locklock];
if([selfisCancelled]){
[selfperformSelector:
@selector(cancelConnection)onThread:
[[selfclass]networkRequestThread]withObject:
nilwaitUntilDone:
NOmodes:
[self.runLoopModesallObjects]];
}elseif([selfisReady]){
self.state=AFOperationExecutingState;
[selfperformSelector:
@selector(operationDidStart)onThread:
[[selfclass]networkRequestThread]withObject:
nilwaitUntilDone:
NOmodes:
[self.runLoopModesallObjects]];
}
[self.lockunlock];
}
由上面的代码我们可以看到在start又通过performSelector与其它线程进行通信。
而这个线程又是怎么来的呢。
继续看代码
+(void)networkRequestThreadEntryPoint:
(id)__unusedobject{
@autoreleasepool{
[[NSThreadcurrentThread]setName:
@"AFNetworking"];
NSRunLoop*runLoop=[NSRunLoopcurrentRunLoop];
[runLoopaddPort:
[NSMachPortport]forMode:
NSDefaultRunLoopMode];
[runLooprun];
}
}
+(NSThread*)networkRequestThread{
staticNSThread*_networkRequestThread=nil;
staticdispatch_once_toncePredicate;
dispatch_once(&oncePredicate,^{
_networkRequestThread=[[NSThreadalloc]initWithTarget:
selfselector:
@selector(networkRequestThreadEntryPoint:
)object:
nil];
[_networkRequestThreadstart];
});
return_networkRequestThread;
}
在networkRequestThread这个类方法中创建了一个NSThread的单例,然后紧接着就start了。
而在初始化这个NSThread的时候又调用了networkRequestThreadEntryPoint主要作用就是获取这个线程的currentRunLoop给它设置一个输入源NSPort,然后再run一下,这样这个线程就一直处于工作状态不退出。
这个又是干啥呢?
继续看代码
-(void)operationDidStart{
[self.locklock];
if(!
[selfisCancelled]){
self.connection=[[NSURLConnectionalloc]initWithRequest:
self.requestdelegate:
selfstartImmediately:
NO];
NSRunLoop*runLoop=[NSRunLoopcurrentRunLoop];
for(NSString*runLoopModeinself.runLoopModes){
[self.connectionscheduleInRunLoop:
runLoopforMode:
runLoopMode];
[self.outputStreamscheduleInRunLoop:
runLoopforMode:
runLoopMode];
}
[self.outputStreamopen];
[self.connectionstart];
}
[self.lockunlock];
dispatch_async(dispatch_get_main_queue(),^{
[[NSNotificationCenterdefaultCenter]postNotificationName:
AFNetworkingOperationDidStartNotificationobject:
self];
});
}
这里NSURLConnection使用的是代理模式来回调,如果不把它设置到runLoop上执行完这个NSOperation就退出,当NSURLConnection的调用delegate的方法时,delegate已经为空。
就不会发生后面的事了。
所以必须将NSURLConnection设置一个NSRunLoop。
另外这个runLoopMode也是��️讲究的,必须是NSRunLoopCommonModes,才能保证手机屏幕在接受手势事件的时候,网络访问不受影响。
参考我写的关于NSRunLoop的博客。
另外说一句NSRunLoop接受的输入源包括NSPort、NSConnection、NSTimer
另外如果发通知又用GCD来切换到主线程发通知,这样看代码比较清晰吧
dispatch_async(dispatch_get_main_queue(),^{
NSNotificationCenter*notificationCenter=[NSNotificationCenterdefaultCenter];
[notificationCenterpostNotificationName:
AFNetworkingOperationDidFinishNotificationobject:
self];
});
{%endhighlight%}
还有一个比较有趣的是下面的代码
{%highlightobjc%}
-(void)resume{
if(!
[selfisPaused]){
return;
}
[self.locklock];
self.state=AFOperationReadyState;
[selfstart];
[self.lockunlock];
}
-(void)setState:
(AFOperationState)state{
if(!
AFStateTransitionIsValid(self.state,state,[selfisCancelled])){
return;
}
[self.locklock];
NSString*oldStateKey=AFKeyPathFromOperationState(self.state);
NSString*newStateKey=AFKeyPathFromOperationState(state);
[selfwillChangeValueForKey:
newStateKey];
[selfwillChangeValueForKey:
oldStateKey];
_state=state;
[selfdidChangeValueForKey:
oldStateKey];
[selfdidChangeValueForKey:
newStateKey];
[self.lockunlock];
}
看到没lock多次,再看lock的类型是NSRecursiveLock即递归锁。
是时候亮亮iOS的同步的法宝了
法宝出处
原子操作(AtomicOperations)
只支持基本类型,比如int什么的,优点:
快。
快到什么程度呢?
它在相同的CPU情况下速度的是互斥锁的四分之一。
但是用的比较少,众多代码中在RAC库中见到过,在这里提一下防止看到代码不知道啥意思就尴尬了。
如RACCommand的
-(void)setAllowsConcurrentExecution:
(BOOL)allowed{
[selfwillChangeValueForKey:
@keypath(self.allowsConcurrentExecution)];
if(allowed){
OSAtomicOr32Barrier(1,&_allowsConcurrentExecution);
}else{
OSAtomicAnd32Barrier(0,&_allowsConcurrentExecution);
}
[selfdidChangeValueForKey:
@keypath(self.allowsConcurrentExecution)];
}
OSMemoryBarrier和volatile
作用上面已经说了,用的也比较少,只在RAC库中见过。
由此可见RAC的代码还是挺屌的啊
各种锁Locks
查看NSLock代码头文件中列了四种NSLock、NSConditionLock、NSRecursiveLock、NSCondition。
既然只有这几个类,说明只有这几个常用,AFNetworking不就是用的NSRecursiveLock锁么,虽然在上面苹果官方文档我看到的是Mutex、Recursivelock、Read-writelock、Distributedlock、Spinlock、Double-checkedlock
Conditions
好吧我也是只在RAC中见到过
@synchronized
这个用的比较多,苹果建立@synchronized的初衷就是方便开发者快速的实现代码同步,语法如下:
@synchronized(obj){
//code
}
有人在测试项目的main.m中写了下面的代码
voidtestSync()
{
NSObject*obj=[NSObjectnew];
@synchronized(obj){
}
}
然后在Xcode中选择菜单Product->PerformAction->Assemble“main.m”,就得到了如下的汇编代码:
_objc_sync_enter、_objc_sync_exit这两个函数应该就是synchronized进入和退出的调用
上objc-sync.mm源码
//Beginsynchronizingon'obj'.
//Allocatesrecursivemutexassociatedwith'obj'ifneeded.
//ReturnsOBJC_SYNC_SUCCESSoncelockisacquired.
intobjc_sync_enter(idobj)
{
intresult=OBJC_SYNC_SUCCESS;
if(obj){
SyncData*data=id2data(obj,ACQUIRE);
assert(data);
data->mutex.lock();
}else{
//@synchronized(nil)doesnothing
if(DebugNilSync){
_objc_inform("NILSYNCDEBUG:
@synchronized(nil);setabreakpointonobjc_sync_niltodebug");
}
objc_sync_nil();
}
returnresult;
}
//Endsynchronizingon'obj'.
//ReturnsOBJC_SYNC_SUCCESSorOBJC_SYNC_NOT_OWNING_THREAD_ERROR
intobjc_sync_exit(idobj)
{
intresult=OBJC_SYNC_SUCCESS;
if(obj){
SyncData*data=id2data(obj,RELEASE);
if(!
data){
result=OBJC_SYNC_NOT_OWNING_THREAD_ERROR;
}else{
boolokay=data->mutex.tryUnlock();
if(!
okay){
result=OBJC_SYNC_NOT_OWNING_THREAD_ERROR;
}
}
}else{
//@synchronized(nil)doesnothing
}
returnresult;
}
typedefstructSyncData{
structSyncData*nextData;
DisguisedPtr
int32_tthreadCount;//numberofTHREADSusingthisblock
recursive_mutex_tmutex;
}SyncData;
synchronized是使用的递归mutex来做同步。
那么@synchronized后面跟的参数是做什么用的呢
看objc_sync_enter里面的SyncData*data=id2data(obj,ACQUIRE);,跟进id2data,看到spinlock_t*lockp=&LOCK_FOR_OBJ(object);再跟进LOCK_FOR_OBJ
#defineLOCK_FOR_OBJ(obj)sDataLists[obj].lock
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- iOS 13 之多 线程