加载中…
正文 字体大小:

了解Unity中的多线程及使用多线程

(2014-11-16 00:07:23)
标签:

unity3d

csharp

thread

多线程

分类: 多线程
==================转载地址=============================
中文翻译地址,由Xiaoke翻译,转载来自1vr.cn:http://blog.1vr.cn/?p=624
英文原始地址,老外的博客,打不开请翻墙:http://unitygems.com/threads/
==================转载地址=============================

==========================================================
转载的风宇冲Unity3D教程学院Unity多线程使用限制
http://blog.sina.com.cn/s/blog_930ffa0b0102v9ll.html
==========================================================

==========================================================
unity3d之使用C#的http函数多线程异步下载资源:
http://www.cnblogs.com/U-tansuo/p/unity3d_Threading_AsyDown_HTTP.html#undefined
==========================================================

==========================================================
分享的U3D里使用多线程的插件:
LOOM-Multi Thread : http://www.u3dchina.com/t-6357-1-1.html
UniExten2.14: http://www.u3dchina.com/t-6300-1-1.html
==========================================================

如果你想在游戏中使用多线程,你应该看看这篇文章,线程是一个相当复杂的话题,但如果你掌握了它,你就可以从容的使用多个硬件处理器或处理很难划分管理数据块.

如在场景中用A*算法进行大量的数据计算.
变形网格中操作大量的顶点.
持续的要运行上传数据到服务器.
二维码识别等图像处理.

如果同时你要处理很多事情或者与Unity的对象互动小可以用thread,否则使用coroutine.

线程是在你程序中与其他线程同时运行的进行.在多处理器的计算机上可以做到多个线程的真正的同步.更多的线程取决于有多个处理核心.

Unity编程时,总有个主线程执行你的代码,也可以创建额外的线程和主线程同时运行.

而Unity中,你仅能从主线程中访问Unity的组件,对象和Unity系统调用.任何企图访问这些项目的第二个线程都将失败并引发错误.这是一个要重视的一个限制.

所以当你写代码时,你认为一个函数开始并达到它执行的点后返回,同样你做的东西又在另外一个函数中执行,但又没有发生相应的变化.操作系统决定你代码的执行,任何时候,你的代码只能暂时”休眠”掉,然后让另外的代码开始运行,

了解Unity中的多线程及使用多线程

在这个例子中,在第一个线程将A的值加载到CPU寄存器中准备+1后被中断,第二个线程来读取A的值,并减去1000,这时A应该是-950.现在第一个线程重新开始,它在寄存器中的50+1的结果存储于A,A变成了51,而-950已经丢掉了.

根本上说,要在用多个线程在同时对变量或内存访问时,要采取很多预防措施来确保不会发生这样的事.

所以Unity决定从另外线程访问这些变量或者内存是无效的,只是为了避免所有系统和框架对象出现问题.

所以要确保一次只有一个线程来修改变量,这不意味着你不能用多线程工作,你可以用”排序”来解决这个问题.

C#中有lock这个关键字,以确保只有一个线程可以在特定时间内访问特定的对象.这里说对象是因为无法锁定一个类型值(value type)或原型(primitive).

int a = 50;

object guard = new object();

void ThreadOneCode()
{
//一些代码在这

lock(guard)
{
a = a + 1;
}

//其余一些代码在这

}

void ThreadTwoCode()
{
//一些代码在这

lock(guard)
{
a = a - 1000;
}

//其余一些代码在这
}

所有都锁定在guard内,保证同一个时间只有一个线程通过guard访问它.你可以使用任何合适的对象.

现在你可能会有各种各样的问题,比如你要锁定的不止一件事,可能是互相嵌套的.那我们该怎么办呢?

我们这个类叫Loom,让你可以轻松在另一个线程运行代码,

这里有两个要注意的功能:

RunAsync(Action)-在另一个线程上运行的一组代码.
QueueOnMainThread(Action,[可选]float time)-运行在主线程的语句(可选延迟).

用Loom.Current访问Loom-创建一个看不见的GameObject用来处理游戏主线程的互动.

下面这个例子用Loom来更新一个网格所有的顶点乘的结果.

//缩放一个网格在第二个线程
void ScaleMesh(Mesh mesh, float scale)
{
//Get the vertices of a mesh
var vertices = mesh.vertices;
//运行一个Action在新的线程
Loom.RunAsync(()=>{
//遍历所有的顶点
for(var i = 0; i < vertices.Length; i++)
{
//缩放顶点
vertices[i] = vertices[i] * scale;
}
//在主线程上运行一些代码
//更新网格
Loom.QueueOnMainThread(()=>{
//设置顶点
mesh.vertices = vertices;
//重新计算边界
mesh.RecalculateBounds();
});

上面这个是个很好的例子,使用lambda函数在第二个线程上做一个没有参数,不需要返回任何内容的操作. closures都是在你自己的类和函数的参数和局部变量的访问.

你可以用 ()=>{ … } 定义一个lambda函数来在新的线程上运行函数内所有的代码.

在主线程上我们需要将修改的网格顶点更新,所以我们使用QueueOnMainThread在接下来的时间更新周期运行处理(此帧或下一帧被称为接下来的更新周期). QueueOnMainThread也需要一个Action来将更新的顶点更新到原来的网格.

这里是Loom的代码:

=====================Loom.cs====================================

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

public class Loom : MonoBehaviour {

    #region 公共字段

    ///
    /// 最大线程数,静态公共字段
    ///
    public static int maxThreads = 8;

    #endregion

    #region 私有字段

    ///
    /// 当前线程数,静态私有字段
    ///
    private static int numThreads;

    ///
    /// 单例模式
    ///
    private static Loom _current;

    private int _count;

    ///
    /// 是否已初始化
    ///
    private static bool initialized;

    private List _actions;
    private List _delayed;
    private List _currentDelayed;

    ///
    /// 当前行为列表
    ///
    private List _currentActions;

    #endregion

    #region 公有属性
   
    ///
    /// 得到当前Loom对象的单例属性
    ///
    public static Loom Current {
        get {
            Initialize();
            return _current;
        }
    }

    #endregion

    #region Unity3d API
   
    void Awake() {
        _current = this;
        initialized = true;
        _actions = new List();
        _delayed = new List();
        _currentDelayed = new List();
        _currentActions = new List();
    }

    void Update ( ) {
        lock ( _actions ) {
            _currentActions.Clear( );
            _currentActions.AddRange(_actions);
            _actions.Clear( );
        }
        foreach ( Action 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( );
        }
    }

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

    #endregion Unity3d API

    ///
    /// 初始化,造一个新的游戏物体Loom,并加一个Loom脚本
    ///
    private static void Initialize() {
        if (!initialized) {
            if (!Application.isPlaying)
                return;
            initialized = true;
            GameObject g = new GameObject("Loom");
            _current = g.AddComponent();
        }
    }
   
    ///
    /// 延时队列项
    ///
    public struct DelayedQueueItem {
        public float time;
        public Action action;
     

    ///
    /// 在主线程上运行的代码
    ///    
    public static void QueueOnMainThread(Action action) {
        QueueOnMainThread(action, 0f);
    }
    ///
    /// 在主线程上运行的代码, 第2个参数为延迟几秒执行, 依赖Time.time执行
    ///   
    public static void QueueOnMainThread(Action action, float time) {
        if (time != 0) {
            lock (Current._delayed) {
                // 增加Time.time的值给time达到延时的效果
                Current._delayed.Add(new DelayedQueueItem { time = Time.time + time, action = action });
            }
        } else {
            lock (Current._actions) {
                Current._actions.Add(action);
            }
        }
    }

    ///
    /// 在新线程上运行的代码
    ///   
    public static Thread RunAsync(Action a) {
        Initialize();
        while (numThreads >= maxThreads) {
            Thread.Sleep(1);
        }
        Interlocked.Increment(ref numThreads);
        ThreadPool.QueueUserWorkItem(RunAction, a);
        return null;
    }

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

=====================Loom.cs====================================

如果是UnityScript,你可以这样使用Loom,将Loom.cs放到plus文件夹下:

//缩放一个网格在第二个线程
function ScaleMesh(mesh : Mesh, scale : float)
{
//Get the vertices of a mesh
var vertices = mesh.vertices;
//运行一个Action在新的线程
Loom.RunAsync(function() {
//遍历所有的顶点
for(var i = 0; i < vertices.Length; i++)
{
//缩放顶点
vertices[i] = vertices[i] * scale;
}
//在主线程上运行一些代码
//更新网格
Loom.QueueOnMainThread(function() {
//设置顶点
mesh.vertices = vertices;
//重新计算边界
mesh.RecalculateBounds();
});

0

阅读 评论 收藏 转载 喜欢 打印举报
  • 评论加载中,请稍候...
发评论

    发评论

    以上网友发言只代表其个人观点,不代表新浪网的观点或立场。

      

    新浪BLOG意见反馈留言板 电话:4006900000 提示音后按1键(按当地市话标准计费) 欢迎批评指正

    新浪简介 | About Sina | 广告服务 | 联系我们 | 招聘信息 | 网站律师 | SINA English | 会员注册 | 产品答疑

    新浪公司 版权所有