探索 unity thread

来源:互联网 发布:115签到软件 编辑:程序博客网 时间:2024/06/06 01:44

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

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

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

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

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来将更新的顶点更新到原来的网格.


如果是UnityScript,你可以这样使用Loom:

//缩放一个网格在第二个线程
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();
});

在unity里边使用多线程做一些事情是非常好的,比如解压资源 更新资源等。因为单开线程的话 不会影响主线程卡顿,这样UI就不会卡了。但是开的线程里边不能执行unity主线程的mono代码。线程启动后,执行完毕自动结束该线程、可以同时启动多个线程做事。

代码如下: using System.Threading;

void StartThread()

    {

        Thread athread = new Thread(new ThreadStart(goThread));

 athread.IsBackground = true;//防止后台现成。相反需要后台线程就设为false

        athread.Start();
    }
 void Awake()
    {
        StartThread();
    }
 object lockd = new object();
    void goThread()
    {
        int index = 0;
        while (true)
        {
            lock (lockd)//防止其他线程访问当前线程使用的数据
            {
                Debug.Log("in thread" + index);
                index++;
                if (index == 100)
                {
                    Thread.Sleep(10000);//   将当前线程挂起指定的时间 毫秒  时间结束后 继续执行下一步  和yield类似
                }
                else if (index == 200)
                {
                    break;//该函数执行完自动结束该线程
                }
            }
        }
    }


所以其实多线程和协程原理差别很大的,只是功能有点类似。


1、当在主线程中创建了一个线程,那么该线程的IsBackground默认是设置为FALSE的。

2、当主线程退出的时候,IsBackground=FALSE的线程还会继续执行下去,直到线程执行结束。

3、只有IsBackground=TRUE的线程才会随着主线程的退出而退出

---------------------------------------------------------------------------------------------

ThreadStart:

Thread athread = new Thread(new ThreadStart(goThread));

        athread.Start();//该方法启动的多线程 不能带有参数。

ParameterThreadStart:

ParameterThreadStart的定义为void ParameterizedThreadStart(object state),使用这个这个委托定义的线程的启动函数可以接受一个输入参数,具体例子如下 :

[csharp] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. <pre name="code" class="cpp">ParameterizedThreadStart threadStart=new ParameterizedThreadStart(Calculate)  
  2. Thread thread=new Thread(threadStart)   
  3. thread.Start(0.9);//参数是0.9  
  4. public void Calculate(object arg)//arg参数是0.9  
  5. {  
  6. double Diameter=double(arg);  
  7. Console.Write("The Area Of Circle with a Diameter of {0} is {1}"Diameter,Diameter*Math.PI);  
  8. }  



Calculate方法只有一个为object类型的参数。需要传多个参数的时候 需要把参数都塞进object 然后进行转换。比如:

一个参数 void BuildA(object para){  List<int> list=para as List<int>;...}  

BuildA(list);

多个参数 void BuildB(object para){  object[] ps= para as object[];List<int> list=ps[0] as as List<int>;GameObject b=ps[1] as GameObject;...} 

 BuildB(new object[]{list,obj});//把多个参数塞到object数组里边 传过去,相当于只传一个参数。


至于是开一个线程还是多个线程 根据需求和内核数SystemInfo.processorCount来确定。最好一个核开一个线程  会快点。

详见bundleglobal.cs和sdFileSystem.cs/sdMultiThread.cs

读写多线程最好的方式是开多个线程去读  保存到临时地方  然后开一个线程去保存,不然如果多个线程同时读和写 肯定会冲突的。


原文地址:http://blog.csdn.net/hany3000/article/details/16917571

                   http://blog.csdn.net/gy373499700/article/details/46970243


0 0
原创粉丝点击