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

*線程與進程

  • 進程
    進程是指在系統中正在運行的一個應用程序
  • 線程
    線程是進程的基本單元,一個進程的多有任務都是在線程中執行的(一個進程至少有一個線程組成)
*多線程

多線程是指從軟件或者是硬件上實現多個線程并發執行的技術。多線程技術使得計算機能夠在同一時間執行多個線程,從而提高其整體的處理性能(解決程序的堵塞,提高程序的執行效率)。打開Nac系統的活動監測器,可以看到當前系統的執行的進程,如圖

系統當前的進程.png
  • 進程作為系統進行分配和調度的基本單位。主要包含三個特征:

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

多線程優/缺點

*優點:
能適當的提高程序的執行效率、資源的利用率(CPU,內存),線程上的任務執行完成后,線程會自動銷毀。
*缺點
開啟線程需要占用一定的內存空間(默認情況下,每個線程都占512kB).
如果開啟大量的線程,會占用大量的內存空間,降低程序的性能.
線程越多,CPU在調用線程的開銷就越大.
程序設計更加復雜,比如線程間的通信、多線程的數據共享.

線程的串行和并行

簡單的講一下,如果在一個進程中,只有一個線程,執行任務時只能一個一個的執行,稱之為“串行”;如果在一個進程中,有多個線程,每條線程之間可以同時執行不同的任務,稱之為“并行”;

主線程

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

多線程技術方案.png

GCD pthread (函數)直接調用
NSThread NSOperation(方法) 面向對象
*最常用的是基于GCD的NSOperation方案

*使用NSThread實現多線程

創建一個NSThread類的實例作為一個線程,一個線程就是一個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>

線程狀態

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

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

測試線程阻塞的代碼

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

線程的屬性

  • 線程的名稱

設置線程名稱可以當線程執行的方法內部出現異常的時候記錄異常和當前線程

  • 線程的優先級

內核調度算法再決定該運行那個線程時,會把線程的優先級作為考量因素,較高的優先級的線程會比低的優先級更具有執行的時間,只是相比較低的優先級的線程,他更有可能被調度器選擇執行而已。切記,不是優先級高,就一定會被先執行。

用于測試優先級的代碼

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

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

線程間的安全隱患

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

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

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

 //剩余票數

 @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張數",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];

}

加鎖后的運行結果.png

加鎖前的運行結果.png
  • 比較兩次的結果,明顯可以看出,加鎖后數據的穩定。

加鎖的語法

   @synchronized (obj) { 
 //插入被修飾的代碼塊
 }
  • obj只是個對象,添加鎖線程對象后,鎖對象就實現了對多線程的監控,保證同一時刻只有一個線程執行,當同步代碼塊執行完以后,鎖對象就會釋放對同步監視器的鎖定(同步鎖只要有一個就可以了,同步鎖監視整個線程的的整個運行狀態,考慮到同步鎖的生命周期,通常推薦使用當前的線程所在的控制器作為同步鎖)
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容