凡是跟UI相關(guān)的都是在主線程執(zhí)行的
UIKit類庫的線程都是不安全的 所以我們需要在主線程上更新UI 因此主線程又叫UI線程
多線程的核心思想 : 就是把耗時操作放在后臺執(zhí)行,避免耗時操作卡死UI
currentThread : 查看當(dāng)前線程
NSLog(@"%@",[NSThread currentThread]);
同步和異步
同步和異步是任務(wù) / 代碼 執(zhí)行的兩種方式
同步--->多個任務(wù)按順序依次執(zhí)行,就是同步執(zhí)行
異步--->多個任務(wù)同時執(zhí)行,就是異步執(zhí)行
pthread
參數(shù)1 : 子線程的ID / 標(biāo)識
在C語言中,一般帶`_t` / `_ref`標(biāo)識數(shù)據(jù)類型
參數(shù)2 : 子線程的屬性,一般傳NULL
NULL : 表示空地址,一般在C語言使用;
nil : 表示空對象,一般在OC使用;
其實,NULLh和nil本質(zhì)上沒有半點兒區(qū)別
參數(shù)3 : 子線程需要執(zhí)行的函數(shù)
void *(*)(void *) : 表示指向函數(shù)的指針,即函數(shù)名;函數(shù)名就是表示函數(shù)地址;
數(shù)組地址就是數(shù)組名或者數(shù)組第0個角標(biāo)元素的地址
void * 表示可以指向任何地址的指針,代表任意數(shù)據(jù)類型;類似于OC的id;
返回值? ? 函數(shù)名? ? 函數(shù)參數(shù)
void *? ? (*)? ? (void *)
參數(shù)4 : 傳入到子線程需要執(zhí)行的函數(shù)的參數(shù)
返回值 : int;在很多C語言框架中,并不是遵守非零既真;因為成功的結(jié)果只有一個,0是唯一的;失敗的原因有很多;
線程調(diào)試 : 查看方法執(zhí)行的線程是否是主線程或者子線程
{number = 1, name = main} : 表示主線程
{number = 3, name = (null)} : 只要number != 1 就表示子線程
提示 : 千萬不要糾結(jié)number到底等于幾,系統(tǒng)分配的
// 參數(shù)1
pthread_t ID;
// 創(chuàng)建了一個子線程,執(zhí)行demo函數(shù)
int result = pthread_create(&ID, NULL, demo, NULL);
// 判斷創(chuàng)建子線程是否成功
if (result == 0) {
NSLog(@"創(chuàng)建子線程成功");
} else {
NSLog(@"創(chuàng)建子線程失敗");
}
子線程執(zhí)行的函數(shù)
*/
void *demo(void *param)
{
NSString *str = (__bridge NSString *)(param);
// currentThread : 查看當(dāng)前線程
NSLog(@"demo %@ - %@",[NSThread currentThread],str);
return NULL;
}
NSThread創(chuàng)建線程三種方式
[self performSelectorInBackground:@selector(demo:) withObject:@"perform"];
以上參數(shù)介紹:
1.NSObject分類方法創(chuàng)建子線程
2.不可以拿到線程對象
3.不需要手動啟動線程
[NSThread detachNewThreadSelector:@selector(demo:) toTarget:self withObject:@"detach"];
以上參數(shù)介紹:
1.類方法創(chuàng)建子線程
2.不可以拿到線程對象
3.不需要手動啟動線程
// 創(chuàng)建線程對象
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(demo:) object:@"alloc"];
// 啟動線程
[thread start];
以上參數(shù)介紹:
1.構(gòu)造方法創(chuàng)建子線程
2.可以拿到線程對象
3.需要手動啟動線程
通知主線程刷新UI
// waitUntilDone : 是否等待updateUI執(zhí)行完,再執(zhí)行后面的代碼,一般傳入NO
[self performSelectorOnMainThread:@selector(updateUI:) withObject:image waitUntilDone:NO];
關(guān)于以上使用調(diào)用方法中-target和selector關(guān)系
@selector中需要傳入一個方法
而target中需要傳入對象告訴系統(tǒng) 這個方法是哪個對象的
線程的聲明周期
提示 : 程序員只能做新建和就緒,其他的都由系統(tǒng)來處理
// 創(chuàng)建線程對象 : 新建狀態(tài)
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(demo) object:nil];
// 啟動線程 : 就緒狀態(tài)(把線程對象添加到可調(diào)度線程池,等待被CPU調(diào)度執(zhí)行)
[thread start];
// sleepForTimeInterval : 使當(dāng)前線程休眠到指定時長
[NSThread sleepForTimeInterval:1.0];
// sleepUntilDate : 使當(dāng)前線程休眠到指定日期
[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:2.0]];
// 使當(dāng)前線程強制死亡
[NSThread exit];
線程的屬性
//新建狀態(tài)
NSThread *thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(demo) object:nil];
// 設(shè)置線程對象的name屬性 : 標(biāo)識一個唯一的線程對象,方便跟蹤
thread1.name = @"t1";
thread1.threadPriority = 1.0;
// 設(shè)置線程對象的優(yōu)先級 : 浮點數(shù);范圍是0.0~1.0;最高是1.0;默認是0.5
// 線程優(yōu)先級不能決定線程執(zhí)行的先后順序,只能決定某個線程有更多的機會被CPU先調(diào)度執(zhí)行完,概率事件
// 注意 : 實際開發(fā)中,千萬不要設(shè)置優(yōu)先級或者服務(wù)器質(zhì)量,會出現(xiàn)意想不到的問題;使用默認的,讓系統(tǒng)自己來處理
// threadPriority : 在目前即將被廢棄,使用qualityOfService來替代;
thread1.qualityOfService = NSQualityOfServiceUserInteractive;
// stackSize : 線程占用內(nèi)存空間大小
NSLog(@"子 %tu",[NSThread currentThread].stackSize / 1024);
多線程的資源共享問題
// 互斥鎖 / 同步鎖 : 使用了線程同步技術(shù)
// 特點 : 可以保證被鎖定的代碼,同一時間只有一個線程可以訪問
// self : 表示互斥鎖的參數(shù);互斥鎖的參數(shù),又叫做鎖對象;
// 鎖對象 : 任何繼承自NSObject的對象,都可以作為互斥鎖的參數(shù);內(nèi)部有把鎖,默認是開啟的
// 鎖對象必須是全局的對象;self是最方便獲取的全局的鎖對象
// 局部鎖對象是鎖不住的,因為每次線程進來之前會新建一把鎖
// 提示 : 加鎖的事情,不是再客戶端操作的;是服務(wù)器加鎖的,多線程資源共享絕大多數(shù)是在服務(wù)器發(fā)生;
// 提示 : 加鎖是犧牲了性能,保證安全.客戶端的性能不能輕易犧牲
@synchronized (self)
{
? ? ? //需要被鎖定的代碼
}
參數(shù)必須傳全局對象 多數(shù)傳self?
原子屬性
使用nonatomic修飾的屬性為非原子屬性
使用atomic修飾的屬性為原子屬性
原子屬性 : 單寫多讀
單寫多讀 : 同一時間只有一個線程可以訪問setter方法;但是可以有多個線程訪問getter方法
注意 : 原子屬性的setter方法是線程安全的,getter方法是線程非安全的
setter方法內(nèi)部有把自旋鎖
自旋鎖 :
?看不見的,由系統(tǒng)封裝的
可以保證被鎖定的代碼,同一時間只能有一個線程可以訪問
一旦外面的線程,發(fā)現(xiàn)代碼被自旋鎖鎖定,外面的線程會以死循環(huán)的方式等待開鎖
互斥鎖 :
可以保證被鎖定的代碼,同一時間只能有一個線程可以訪問
一旦外面的線程,發(fā)現(xiàn)代碼被互斥鎖鎖定,外面的線程會進入就緒狀態(tài),等待開鎖