Unity多線程使用

<meta charset="utf-8">

一聘鳞、什么是線程?

線程是操作系統(tǒng)能夠進行運算調度的最小單位锹雏,被包含在進程之中巴比,是進程中的實際運作單位。一條線程指的是進程中一個單一順序的控制流礁遵,一個進程中可以并發(fā)多個線程轻绞,每條線程并行執(zhí)行不同的任務。

簡單理解:

我們首先了解一下什么是進程佣耐。我們電腦開啟的每個軟件其實就是一個進程政勃。Ctrl+alt+delete 選擇任務管理器可以查看

image.png

為什么要先了解進程呢?因為進程和線程是包含關系兼砖,一個進程(軟件)中是包含多個線程的奸远。并且一個進程至少要有一個線程。

好接下來我們舉例說明一下讽挟,QQ這個進程(軟件)懒叛,我們把進程和線程的關系當成腳本中我們寫的類和函數的關系,要知道我們的類其實就是一個大功能(軟件)耽梅,類中的方法函數就是為了幫助實現這個類的某個功能(軟件的某個功能)薛窥。他們之間的關系 就是 一個類中包含多個方法函數并且類中至少有一個方法(一個類中一個方法都沒有,沒有操作計算沒有意義眼姐,這里舉例參數類不算.....)诅迷。

二、為什么要使用線程众旗?

線程其實是同時(并行)執(zhí)行的竟贯,要知道在Unity中雖然有協程可以協助主線程進行計算,但是協程的計算還是在主線程中的逝钥,如果協程要計算的數據過大屑那,需要等待拱镐,這時候就會影響主線程的其他方法執(zhí)行,比如我們在UpDate中實現鼠標控制相機旋轉移動持际,這時候協程計算某個數據等待了2秒沃琅,你就明顯發(fā)現屏幕卡頓了(因為主線程在計算東西,相機旋轉在后面等著呢)蜘欲。

所以這時候就用到線程了益眉。有了線程 我不管你計算的數據多么龐大,我主線程根本不怕(兩者各干各的 互不影響)姥份。

三郭脂、Unity可以使用多線程,但卻要避免使用線程

Unity自己本身UnityEngine所使用的API是不能被多線程調用的澈歉,所以Unity是不能使用多線程的展鸡,但是C#中可以使用多線程,Unity使用C#進行腳本編輯埃难,故而Unity也可以通過C#來調用多線程莹弊。

Unity使用多線程時要注意幾點:

1.變量都是共享的(都能指向相同的內存地址)

2.UnityEngine 的 API 不能在分線程運行

3.UnityEngine 定義的基本結構(int, float, struct 定義的數據類型)可以在分線程計算,如 Vector3(struct)可以, 但 Texture2d(class,根父類為 Object) 不可以涡尘。

4.UnityEngine 定義的基本類型的函數可以在分線程運行

四忍弛、線程的生命周期

image.png

線程的生命周期包含5個階段,包括:新建考抄、就緒细疚、運行、阻塞川梅、銷毀惠昔。

  • 新建:就是剛使用new方法,new出來的線程挑势;
  • 就緒:就是調用的線程的start()方法后镇防,這時候線程處于等待CPU分配資源階段,誰先搶的CPU資源潮饱,誰開始執(zhí)行;
  • 運行:當就緒的線程被調度并獲得CPU資源時来氧,便進入運行狀態(tài),run方法定義了線程的操作和功能;
  • 阻塞:在運行狀態(tài)的時候香拉,可能因為某些原因導致運行狀態(tài)的線程變成了阻塞狀態(tài)啦扬,比如sleep()、wait()之后線程就處于了阻塞狀態(tài)凫碌,這個時候需要其他機制將處于阻塞狀態(tài)的線程喚醒扑毡,比如調用notify或者notifyAll()方法。喚醒的線程不會立刻執(zhí)行run方法盛险,它們要再次等待CPU分配資源進入運行狀態(tài);
  • 銷毀:如果線程正常執(zhí)行完畢后或線程被提前強制性的終止或出現異常導致結束瞄摊,那么線程就要被銷毀勋又,釋放資源;

五、線程基礎方法使用

new Thread():創(chuàng)建一個線程

start():開啟創(chuàng)建的線程

join():當前線程等待另一個線程結束后换帜,在執(zhí)行

Sleep();等待N毫秒后繼續(xù)執(zhí)行

Suspend():該方法并不終止未完成的線程楔壤,它僅僅掛起當前線程,以后還可恢復惯驼;

Resume():恢復被Suspend()方法掛起的線程的執(zhí)行蹲嚣。

Abort():結束線程

其中 Suspend(),Resume()已經過時祟牲,因為它們是不安全的隙畜,為什么呢?這涉及到CPU的調度問題说贝,人們?yōu)榱藢PU充分的利用起來议惰,在數據處理時,CPU的調度是不確定狂丝,舉個例子换淆,我們在生活中給別人打電話的時候左手執(zhí)行接聽電話這個任務哗总,這時候電話那頭人說了一個號碼几颜,你需要用右手來記錄,雖然在同一時間執(zhí)行了這兩個任務讯屈,但是當你用左手接聽電話那邊說的數字的時候蛋哭,右手是不可能同時寫的(除非你有預言功能,在他說之前就已經知道號碼了)涮母,這時候的處理方式就是當他說出一部分號碼時谆趾,右手趁著他喘口氣的時間,在本子上記下來叛本。CPU的處理方式也是如此沪蓬。如果我們在左手接電話的任務中調用Suspend()方法,這時候右手任務趁虛而入来候,那么我們會發(fā)現我們想掛起的左手任務還在執(zhí)行跷叉,不想掛起的任務卻被暫停了,這樣在數據處理中是一個很嚴重的事情营搅。

接下來教大家上面方法如何使用

1.線程調用有參無參的方法函數

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Threading;

public class ThreadTest : MonoBehaviour
{
    Thread threadA;
    Thread threadB;
    void Start()
    {
        threadA = new Thread(AA);
        threadB = new Thread(new ParameterizedThreadStart(BB));
        threadA.Start();
        threadB.Start("B線程: ");
    }

    //無參 
    void AA()
    {
        for (int i = 0; i < 5; i++)
        {
            Debug.Log("A線程: " + i);
        }
    }

    //有參 注意有參函數類型必須是object類型
    void BB(object a) 
    {
        for (int i = 0; i < 5; i++)
        {
            Debug.Log(a.ToString() + i);
        }
    }

}

運行結果:

image.png

我們可以看出打印結果是無序的(雖然是交錯打印的云挟,實際他們的運行方式是,A線程在打印的時候 CPU有空余時間转质,這時候B線程直接頂上园欣,這么做能充分的利用CPU)

2.Join()方法使用

我們接下來讓A線程循環(huán)打印完畢后在執(zhí)行B線程循環(huán),這就用到我們的Join方法了

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Threading;

public class ThreadTest : MonoBehaviour
{
    Thread threadA;
    Thread threadB;
    void Start()
    {
        threadA = new Thread(AA);
        threadB = new Thread(new ParameterizedThreadStart(BB));
        threadA.Start();
        threadA.Join();//在CPU加入threadA的結束判斷當threadA線程結束后 在執(zhí)行后面的線程方法
        threadB.Start("A線程: ");
    }

    //無參
    void AA()
    {
        for (int i = 0; i < 5; i++)
        {
            Debug.Log("B線程: " + i);
        }
    }

    //有參
    void BB(object a) 
    {
        for (int i = 0; i < 5; i++)
        {
            Debug.Log(a.ToString() + i);
        }
    }

}

結果:

image.png

看結果就知道變成順序打印了休蟹,但是要注意一點 Join這個方法會占用很多CPU資源沸枯,要小心利用日矫,Join在線程執(zhí)行完之前分配大量的時間片給該線程,直到線程結束后辉饱。所以使用的時候要注意

3.Sleep()等待睡眠結束

這個很簡單 就是執(zhí)行到該語句時等待一段時間繼續(xù)往下執(zhí)行

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Threading;

public class ThreadTest : MonoBehaviour
{
    Thread threadA;
    Thread threadB;
    void Start()
    {
        threadA = new Thread(AA);
        threadB = new Thread(new ParameterizedThreadStart(BB));
        threadA.Start();
        threadB.Start("B線程: ");
    }

    //無參
    void AA()
    {
        Debug.Log("A線程開啟");
        Thread.Sleep(3000); //3000毫秒 等待3秒鐘
        Debug.Log("A線程等待了3秒");

    }

    //有參
    void BB(object a) 
    {
        for (int i = 0; i < 3; i++)
        {
            Thread.Sleep(1000); //1000毫秒 等待1秒鐘
            Debug.Log(a.ToString() + i);
        }
    }
}

結果:

3d64e33a69ea4a338275eacc37bced05.gif

結果很直觀吧 就不多解釋了搬男。

4.Abort() 停止線程

其實也叫"殺死"線程,執(zhí)行這個方法后線程就被摧毀了彭沼,從線程生命周期來看他是自動執(zhí)行的缔逛,這里舉一個使用他的例子

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Threading;

public class ThreadTest : MonoBehaviour
{
    Thread threadA;
    void Start()
    {
        threadA = new Thread(AA);
        threadA.Start();
    }

    //無參
    void AA()
    {
        //死循環(huán) 每過1秒執(zhí)行一次
        while (true)
        {
            Thread.Sleep(1000); //3000毫秒 等待3秒鐘
            Debug.Log("A線程執(zhí)行");
        }
    }

    void OnApplicationQuit()
    {
        //結束線程必須關閉 否則下次開啟會出現錯誤 (如果出現的話 只能重啟unity了)
        threadA.Abort();
    }

}

結果:

![892b3b6a7fa34048b8fbbab49c4c69b8.gif](https://upload-images.jianshu.io/upload_images/26067915-7cdcbafd6f4c9ec6.gif?imageMogr2/auto-orient/strip)

這里打印結果沒啥作用,其實你們可以試一下姓惑,在OnApplicationQuit()不加threadA.Abort()方法的時候褐奴,Unity結束運行時你會發(fā)現還是會有打印效果。

5.做一個按鈕控制開關線程

using System.Collections;
using System.Collections.Generic;
using UnityEngine.UI;
using UnityEngine;
using System.Threading;

public class ThreadTest : MonoBehaviour
{
    [SerializeField] Button btn;
    Thread threadA;
    void Start()
    {
        threadA = new Thread(AA);
        threadA.Start();

        btn.onClick.AddListener(delegate {
            btn.transform.GetChild(0).GetComponent<Text>().text = isStart ? "開啟" : "暫停";
            isStart = !isStart; 
        });
    }

    bool isStart = false;
    //無參
    void AA()
    {
        //死循環(huán) 每過1秒執(zhí)行一次
        while (true)
        {
            if (isStart)
            {
                Debug.Log("A線程執(zhí)行");
                Thread.Sleep(1000); //1000毫秒 等待1秒鐘
            }
        }
    }

    void OnApplicationQuit()
    {
        //結束線程必須關閉 否則下次開啟會出現錯誤 (如果出現的話 只能重啟unity了)
        threadA.Abort();
    }

}

結果:

892b3b6a7fa34048b8fbbab49c4c69b8.gif

6.使用協程控制線程一秒打印一次

using System.Collections;
using System.Collections.Generic;
using UnityEngine.UI;
using UnityEngine;
using System.Threading;
using System.IO;

public class ThreadTest : MonoBehaviour
{
    Thread threadA;
    void Start()
    {
        threadA = new Thread(AA);
        threadA.Start();
        StartCoroutine(Test());
    }

    IEnumerator Test()
    {
        while (true)
        {
            isEnd = true;
            yield return new WaitForSeconds(1f);
        }
    }

    bool isEnd = false;
    //無參
    void AA()
    {
        while (true)
        {
            if (isEnd)
            {
                isEnd = false;
                Debug.Log("A線程執(zhí)行");
            }
        }

    }

    void OnApplicationQuit()
    {
        //結束線程必須關閉 否則下次開啟會出現錯誤 (如果出現的話 只能重啟unity了)
        threadA.Abort();
    }

}

六于毙、線程池的使用

.NET Framework的ThreadPool類提供一個線程池敦冬,該線程池可用于執(zhí)行任務、發(fā)送工作項唯沮、處理異步 I/O脖旱、代表其他線程等待以及處理計時器。那么什么是線程池介蛉?線程池其實就是一個存放線程對象的“池子(pool)”萌庆,他提供了一些基本方法,如:設置pool中最小/最大線程數量币旧、把要執(zhí)行的方法排入隊列等等践险。ThreadPool是一個靜態(tài)類,因此可以直接使用吹菱,不用創(chuàng)建對象巍虫。

有點類似Unity中的對象池,當要使用線程的時候我們線程池查找是否有空閑的線程鳍刷,有就使用占遥,沒有就創(chuàng)建生成。

微軟官網說法如下:

許多應用程序創(chuàng)建大量處于睡眠狀態(tài)输瓜,等待事件發(fā)生的線程瓦胎。還有許多線程可能會進入休眠狀態(tài),這些線程只是為了定期喚醒以輪詢更改或更新的狀態(tài)信息前痘。 線程池凛捏,使您可以通過由系統(tǒng)管理的工作線程池來更有效地使用線程。

所以線程池一般是在需要大量線程芹缔,并且線程的數據處理都很小的情況下使用

使用方法很簡單:

using System.Collections;
using System.Collections.Generic;
using UnityEngine.UI;
using UnityEngine;
using System.Threading;
using System.IO;

public class ThreadTest : MonoBehaviour
{
    void Start()
    {
        ThreadPool.QueueUserWorkItem(new WaitCallback(AA), null);
    }

    //無參
    void AA(object a)
    {
        Debug.Log("A線程執(zhí)行");
    }

}

這里要注意幾點:

1.線程池的方法必須是有參方法坯癣,而且傳參不能超過2個
2.如果方法使用死循環(huán),Unity結束運行后最欠,還是會執(zhí)行線程方法示罗。

?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末惩猫,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子蚜点,更是在濱河造成了極大的恐慌轧房,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,482評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件绍绘,死亡現場離奇詭異奶镶,居然都是意外死亡,警方通過查閱死者的電腦和手機陪拘,發(fā)現死者居然都...
    沈念sama閱讀 88,377評論 2 382
  • 文/潘曉璐 我一進店門厂镇,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人左刽,你說我怎么就攤上這事捺信。” “怎么了欠痴?”我有些...
    開封第一講書人閱讀 152,762評論 0 342
  • 文/不壞的土叔 我叫張陵迄靠,是天一觀的道長。 經常有香客問我喇辽,道長掌挚,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,273評論 1 279
  • 正文 為了忘掉前任茵臭,我火速辦了婚禮疫诽,結果婚禮上舅世,老公的妹妹穿的比我還像新娘旦委。我一直安慰自己,他們只是感情好雏亚,可當我...
    茶點故事閱讀 64,289評論 5 373
  • 文/花漫 我一把揭開白布缨硝。 她就那樣靜靜地躺著,像睡著了一般罢低。 火紅的嫁衣襯著肌膚如雪查辩。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,046評論 1 285
  • 那天网持,我揣著相機與錄音宜岛,去河邊找鬼。 笑死功舀,一個胖子當著我的面吹牛萍倡,可吹牛的內容都是我干的。 我是一名探鬼主播辟汰,決...
    沈念sama閱讀 38,351評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼列敲,長吁一口氣:“原來是場噩夢啊……” “哼阱佛!你這毒婦竟也來了?” 一聲冷哼從身側響起戴而,我...
    開封第一講書人閱讀 36,988評論 0 259
  • 序言:老撾萬榮一對情侶失蹤凑术,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后所意,有當地人在樹林里發(fā)現了一具尸體淮逊,經...
    沈念sama閱讀 43,476評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 35,948評論 2 324
  • 正文 我和宋清朗相戀三年扶踊,在試婚紗的時候發(fā)現自己被綠了壮莹。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,064評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡姻檀,死狀恐怖命满,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情绣版,我是刑警寧澤胶台,帶...
    沈念sama閱讀 33,712評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站杂抽,受9級特大地震影響诈唬,放射性物質發(fā)生泄漏。R本人自食惡果不足惜缩麸,卻給世界環(huán)境...
    茶點故事閱讀 39,261評論 3 307
  • 文/蒙蒙 一铸磅、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧杭朱,春花似錦阅仔、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至刃唐,卻和暖如春羞迷,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背画饥。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評論 1 262
  • 我被黑心中介騙來泰國打工衔瓮, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人抖甘。 一個月前我還...
    沈念sama閱讀 45,511評論 2 354
  • 正文 我出身青樓热鞍,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子碍现,可洞房花燭夜當晚...
    茶點故事閱讀 42,802評論 2 345

推薦閱讀更多精彩內容