我寫了新文章 《iOS高級開發(fā)之多線程編程之一》

*線程與進(jìn)程

  • 進(jìn)程
    進(jìn)程是指在系統(tǒng)中正在運(yùn)行的一個(gè)應(yīng)用程序
  • 線程
    線程是進(jìn)程的基本單元,一個(gè)進(jìn)程的多有任務(wù)都是在線程中執(zhí)行的(一個(gè)進(jìn)程至少有一個(gè)線程組成)
*多線程

多線程是指從軟件或者是硬件上實(shí)現(xiàn)多個(gè)線程并發(fā)執(zhí)行的技術(shù)。多線程技術(shù)使得計(jì)算機(jī)能夠在同一時(shí)間執(zhí)行多個(gè)線程,從而提高其整體的處理性能(解決程序的堵塞,提高程序的執(zhí)行效率)。打開Nac系統(tǒng)的活動監(jiān)測器,可以看到當(dāng)前系統(tǒng)的執(zhí)行的進(jìn)程,如圖

系統(tǒng)當(dāng)前的進(jìn)程.png
  • 進(jìn)程作為系統(tǒng)進(jìn)行分配和調(diào)度的基本單位。主要包含三個(gè)特征:

一、獨(dú)立性
進(jìn)程是一個(gè)能夠獨(dú)立運(yùn)行的基本單位,它既擁有自己獨(dú)立的資源,又擁有著自己獨(dú)立的私有空間。在沒有經(jīng)過進(jìn)程本身的允許情況下,一個(gè)用戶的進(jìn)程是不可以直接訪問其他進(jìn)程的地址空間。
二、動態(tài)性
進(jìn)程的實(shí)質(zhì)就是程序在系統(tǒng)中的一次執(zhí)行過程。進(jìn)程有自己的生命周期和各自不同的狀態(tài),進(jìn)程是靜態(tài)消亡的。
三、并發(fā)性
多個(gè)進(jìn)程可以在單個(gè)處理器上同事執(zhí)行,而互不影響。

多線程優(yōu)/缺點(diǎn)

*優(yōu)點(diǎn):
能適當(dāng)?shù)奶岣叱绦虻膱?zhí)行效率、資源的利用率(CPU,內(nèi)存),線程上的任務(wù)執(zhí)行完成后,線程會自動銷毀。
*缺點(diǎn)
開啟線程需要占用一定的內(nèi)存空間(默認(rèn)情況下,每個(gè)線程都占512kB).
如果開啟大量的線程,會占用大量的內(nèi)存空間,降低程序的性能.
線程越多,CPU在調(diào)用線程的開銷就越大.
程序設(shè)計(jì)更加復(fù)雜,比如線程間的通信、多線程的數(shù)據(jù)共享.

線程的串行和并行

簡單的講一下,如果在一個(gè)進(jìn)程中,只有一個(gè)線程,執(zhí)行任務(wù)時(shí)只能一個(gè)一個(gè)的執(zhí)行,稱之為“串行”;如果在一個(gè)進(jìn)程中,有多個(gè)線程,每條線程之間可以同時(shí)執(zhí)行不同的任務(wù),稱之為“并行”;

主線程

一個(gè)程序運(yùn)行后,系統(tǒng)會默認(rèn)的開啟一個(gè)線程,稱之為“主線程”/“UI線程”。
主線程一般用來刷新UI界面,處理UI事件(點(diǎn)擊、滾動、拖動等事件)。
****為了保持操作的流暢,一般不會將耗時(shí)的操作放在主線程中執(zhí)行。

多線程技術(shù)方案.png

GCD pthread (函數(shù))直接調(diào)用
NSThread NSOperation(方法) 面向?qū)ο?br> *最常用的是基于GCD的NSOperation方案

*使用NSThread實(shí)現(xiàn)多線程

創(chuàng)建一個(gè)NSThread類的實(shí)例作為一個(gè)線程,一個(gè)線程就是一個(gè)NSThread對象。

<pre>//方式一

NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(demo) object:nil];

NSThread *thread 1= [[NSThread alloc]initWithTarget:self selector:@selector(demo) object:@"小明"];

[thread start];

//方式二

[NSThread detachNewThreadSelector:@selector(demo) toTarget:self withObject:nil];

//方式三

[self performSelectorInBackground:@selector(demo) withObject:nil];

  • (void) demo{

NSLog(@"hello thread ");

}

  • (void) demo :(NSString *)name{

NSLog(@"hello thread %@ ",name);

} </pre>

線程狀態(tài)

話不多說,直接用向大家來展示一下

線程狀態(tài)的切換.png
  • 線程阻塞時(shí)的兩個(gè)方法
+ (void)sleepUntilDate:(NSDate *)date;
+ (void)sleepForTimeInterval:(NSTimeInterval)ti; 比較常用

測試線程阻塞的代碼

 // 創(chuàng)建一個(gè)線程(當(dāng)線程結(jié)束,不能再次使用)
NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(demo) object:nil];
[thread start];
- (void)demo{
for (int i = 0; i <= 20; i++) {
NSLog(@"當(dāng)前的i的值%d",i);
//當(dāng)線程阻塞時(shí)
if (i == 5) {
[NSThread sleepForTimeInterval:3.0];
}
//線程死亡
if (i  == 10) {
[NSThread exit];
}
}
}

線程的屬性

  • 線程的名稱

設(shè)置線程名稱可以當(dāng)線程執(zhí)行的方法內(nèi)部出現(xiàn)異常的時(shí)候記錄異常和當(dāng)前線程

  • 線程的優(yōu)先級

內(nèi)核調(diào)度算法再決定該運(yùn)行那個(gè)線程時(shí),會把線程的優(yōu)先級作為考量因素,較高的優(yōu)先級的線程會比低的優(yōu)先級更具有執(zhí)行的時(shí)間,只是相比較低的優(yōu)先級的線程,他更有可能被調(diào)度器選擇執(zhí)行而已。切記,不是優(yōu)先級高,就一定會被先執(zhí)行。

用于測試優(yōu)先級的代碼

 //多線程操作
   - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
// 創(chuàng)建第一個(gè)線程
NSThread *thread1 = [[NSThread alloc]initWithTarget:self selector:@selector(demo) object:nil];
//程序的優(yōu)先級
thread1.threadPriority = 1;
//就緒狀態(tài)
[thread1 start];
//名稱
thread1.name = @"thread1";
// 創(chuàng)建第二個(gè)線程
NSThread *thread2 = [[NSThread alloc]initWithTarget:self selector:@selector(demo) object:nil];
//程序的優(yōu)先級
thread2.threadPriority = 0;
//就緒狀態(tài)
[thread2 start];
//名稱
thread2.name = @"thread2";
}

- (void)demo{
for (int i = 0; i <= 20; i++) {
    NSLog(@"當(dāng)前的i的值%d,%@",i,[NSThread currentThread]);
    
}
優(yōu)先級的取值范圍.png

線程間的安全隱患

模擬賣票程序.png
  • 兩個(gè)程序同時(shí)讀取當(dāng)前的票數(shù)為1000,然后窗口一賣出一張票,是票數(shù)剩余999,同時(shí)窗口二也售出一張票,使票數(shù)變成999。結(jié)果售出兩張票,票數(shù)卻為999,這就造成了數(shù)據(jù)的錯(cuò)誤。我們可以通過枷鎖的方式來解決這個(gè)問題。
加鎖前
加鎖后.png

*通過加鎖可以保證某一個(gè)時(shí)刻只能有一個(gè)線程訪問資源,防止了其他的線程搶奪資源。

下面通過一個(gè)賣票案列(加鎖),來讓大家更好的理解線程安全問題

 //剩余票數(shù)

 @property (nonatomic, assign) int leftTicketCount;
 @end

 @implementation ViewController
 //買票
 - (void)saleTickets:(NSString *)welcome{

 while (true) {
    // 模擬延遲
    [NSThread sleepForTimeInterval:1.0];
    //添加鎖
    @synchronized (self) {
        //判斷是否有票
        if (self.leftTicketCount > 0) {
            self.leftTicketCount--;
            NSLog(@"售票處--%@,%@賣了一張票,剩余%d張數(shù)",welcome,[NSThread currentThread].name,self.leftTicketCount);
            
        }else{
            NSLog(@"沒有票了");
            return;
        }
    }
}

}




- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
self.leftTicketCount = 20;
NSThread *thread1 = [[NSThread alloc]initWithTarget:self selector:@selector(saleTickets:) object:@"歡迎"];
 NSThread *thread2 = [[NSThread alloc]initWithTarget:self selector:@selector(saleTickets:) object:@"哈哈"];
thread1.name = @"1號窗口";
thread2.name = @"2號窗口";
[thread1 start];
[thread2 start];

}

加鎖后的運(yùn)行結(jié)果.png

加鎖前的運(yùn)行結(jié)果.png
  • 比較兩次的結(jié)果,明顯可以看出,加鎖后數(shù)據(jù)的穩(wěn)定。

加鎖的語法

   @synchronized (obj) { 
 //插入被修飾的代碼塊
 }
  • obj只是個(gè)對象,添加鎖線程對象后,鎖對象就實(shí)現(xiàn)了對多線程的監(jiān)控,保證同一時(shí)刻只有一個(gè)線程執(zhí)行,當(dāng)同步代碼塊執(zhí)行完以后,鎖對象就會釋放對同步監(jiān)視器的鎖定(同步鎖只要有一個(gè)就可以了,同步鎖監(jiān)視整個(gè)線程的的整個(gè)運(yùn)行狀態(tài),考慮到同步鎖的生命周期,通常推薦使用當(dāng)前的線程所在的控制器作為同步鎖)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容