Unity中多線程設計與實現

前言

因爲項目後期,好久沒有更博了。最近項目推上線了,突然閒下來了。想寫的東西很多。就先從多線程開始吧。
個人對線程的理解還不夠透徹,希望如果有更好的看法可以在評論區提出,感謝。

什麼多線程

在一個程序中,一些獨立運行的程序片段叫作“線程”(Thread),利用它編程的概念就叫作“多線程處理”(Multithreading)。

在Unity中使用多線程能幹什麼?

  1. 使用Unity開發的應用,只能在主線程中訪問Unity組件,這一點限制了部分的優化。不過小夥伴可以開一些輔助線程。

  2. 允許開線程來輔助的功能業務

    • 大量的數據計算 (eg. A*尋路算法之類的)
    • IO操作
    • 解壓縮資源
    • 不涉及Unity組件的資源加載(Ps.關於涉及Unity組件的加載可以通過邏輯剝離來實現,這邊只拋出一個idea供大家發散。)

舉栗子的時間到了

  • 首先就是繼承MonoBehaviour啦。
/*----------------------------------------------------------------
            // Copyright © 2016 Jhon
            // 
            // FileName: Loom.cs
            // Describle:
            // Created By:  Jhon
            // Date&Time:  2016年10月14日 17:31:26
            // Modify History: 
            //
//--------------------------------------------------------------*/
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System;
using System.Threading;

public class Loom : MonoBehaviour
{

}
  • 定義一個線程處理的 Item 結構,以及變量的定義。
#region StructMember
    public struct DelayedQueueItem
    {
        public float time;
        public Action action;
    }
#endregion

#region Member
    public static int mMaxThreads = 8;

    private static Loom _current;
    public static Loom Current
    {
        get
        {
            Initialize();
            return _current;
        }
    }

    private static int mNumThreads;
    private static bool initialized;

    private int _count;
    private List<Action> _actions = new List<Action>();
    private List<Action> _currentActions = new List<Action>();
    private List<DelayedQueueItem> _delayed = new List<DelayedQueueItem>();
    private List<DelayedQueueItem> _currentDelayed = new List<DelayedQueueItem>();

#endregion
  • 藉助MonoBehaviour的生命週期來處理業務邏輯
#region MonoLife

    void Awake()
    {
        _current = this;
        initialized = true;
    }

    void OnEnable()
    {
        if (null == _current)
        {
            _current = this;
        }
    }

    void OnDisable()
    {
        if (this == _current)
        {
            _current = null;
        }
    }

    void Update()
    {
        lock (_actions)
        {
            _currentActions.Clear();
            _currentActions.AddRange(_actions);
            _actions.Clear();
        }

        for (int i = 0;i < _currentActions.Count;i++)
        {
            _currentActions[i]();
        }

        lock (_delayed)
        {
            _currentDelayed.Clear();

            for(int i = _delayed.Count - 1;i > 0;i--)
            {
                if(_delayed[i].time > Time.time)
                {
                    continue;
                }

                _currentDelayed.Add(_delayed[i]);
                _delayed.RemoveAt(i);

            }
        }

        for(int i = 0;i < _currentDelayed.Count;i++)
        {
            _currentDelayed[i].action();
        }

    }

#endregion
  • 業務邏輯
#region BusinessLogic

    private static void Initialize()
    {
        if (initialized == true)return;
        if (Application.isPlaying == false)return;

        initialized = true;
        var g = new GameObject("Loom");
        _current = g.AddComponent<Loom>();

    }

    private static void RunAction(object action)
    {
        try
        {
            ((Action)action)();
        }
        catch(Exception e)
        {
            Log.Error(e.Message);
        }
        finally
        {
            Interlocked.Decrement(ref mNumThreads);
        }
    }

#endregion
  • 單例模式對外提供的API
#region PublicTools
    public static void QueueOnMainThread(Action action, float time = 0.0f)
    {
        if (time != 0)
        {
            lock (Current._delayed)
            {
                Current._delayed.Add(new DelayedQueueItem { time = Time.time + time, action = action });
            }
        }
        else
        {
            lock (Current._actions)
            {
                Current._actions.Add(action);
            }
        }
    }

    public static Thread RunAsync(Action varAction)
    {
        Initialize();
        while (mNumThreads >= mMaxThreads)
        {
            Thread.Sleep(1);
        }
        Interlocked.Increment(ref mNumThreads);
        ThreadPool.QueueUserWorkItem(RunAction, varAction);
        return null;
    }
#endregion

寫在最後

Update函數中可以使用foreach來進行遍歷,在我的 另一篇博客 中有提到使用foreach會有16BGC。鑑於是在Update中進行的操作,推薦使用for來進行遍歷。下面貼出foreach版本。

    private void OldVersionUpdate()
    {
        lock (_actions)
        {
            _currentActions.Clear();
            _currentActions.AddRange(_actions);
            _actions.Clear();
        }
        foreach (var a in _currentActions)
        {
            a();
        }
        lock (_delayed)
        {
            _currentDelayed.Clear();
            _currentDelayed.AddRange(_delayed.Where(d => d.time <= Time.time));
            foreach (var item in _currentDelayed)
                _delayed.Remove(item);
        }
        foreach (var delayed in _currentDelayed)
        {
            delayed.action();
        }
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章