unity与多线程,子线程的崩溃会卡死主线程

  • Post author:
  • Post category:其他


http://blog.sina.com.cn/s/blog_471132920101hh5d.html

https://www.jianshu.com/p/95784290a384

https://www.cnblogs.com/HangZhe/p/7273227.html

https://kb.cnblogs.com/page/88513/

https://www.cnblogs.com/OceanEyes/p/coroutine_vs_threading.html

https://github.com/rhedgeco/unity_multithreading_handler 代码

举例1:使用多线程

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

public class UseThread : MonoBehaviour
{
    private int count = 0;
    void Start()
    {
        Thread t1 = new Thread(SayHello);
        Thread t2 = new Thread(SayNihao);
        t1.Name = "t1";
        t2.Name = "t2";
        t1.Start("xiaoming");
        t2.Start(123);
    }

    private void SayHello(object param)
    {
        for (int i = 0; i < 300; ++i)
        {
            Debug.LogError("hello " + param.ToString() + "  " + Thread.CurrentThread.Name + "  " + Thread.CurrentThread.ManagedThreadId);
            Thread.Sleep(1000);
        }
    }

    private void SayNihao(object param)
    {
        for (int i = 0; i < 300; ++i)
        {
            Debug.LogError("你好 " + param.ToString() + "  " + Thread.CurrentThread.Name + "  " + Thread.CurrentThread.ManagedThreadId);
            Thread.Sleep(1000);
        }
    }

    void Update()
    {
        count++;
        if (count % 1000 == 0)
        {
            Debug.LogError("mainThread " + Thread.CurrentThread.Name + "  " + Thread.CurrentThread.ManagedThreadId);
            count = 0;
        }
    }
}

上面在start方法中,创建了两个子线程。

线程1:执行SayHello方法

线程2:执行SayNihao方法

主线程:执行Update方法

举例2:两个子线程同时访问queue

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

public class UseQueueInMultiThread : MonoBehaviour
{
    private int count = 0;
    private Queue<string> m_queue = new Queue<string>();
    private static readonly object m_lock = new object();
    void Start()
    {
        Thread t1 = new Thread(SayHello);
        Thread t2 = new Thread(SayNihao);
        t1.Name = "t1";
        t2.Name = "t2";
        t1.Start("xiaoming");
        t2.Start(123);
    }

    private void SayHello(object param)
    {
        for (int i = 0; i < 300; ++i)
        {
            lock(m_lock) //如果未加锁,则导致最后输出的m_queue.count不等于总共的600
            {
                m_queue.Enqueue(i + "   " + param.ToString());
            }
            Thread.Sleep(10);
        }
    }

    private void SayNihao(object param)
    {
        for (int i = 0; i < 300; ++i)
        {
            lock (m_lock) //如果未加锁,则导致最后输出的m_queue.count不等于总共的600
            {
                m_queue.Enqueue(i + "   " + param.ToString());
            }
            Thread.Sleep(10);
        }
    }

    void Update()
    {
        count++;
        if (count % 200 == 0)
        {
            Debug.LogError("queue count=" + m_queue.Count);
            count = 0;
        }
    }
}

上面如果将lock(m_lock)去除掉,则会看到在update中最后的输出,有可能不等于600。

举例3:主线程的卡死问题

这个问题,还是很难,或者很容易解决的。

lock的最后编译成的是:

try
{
	Monitor.Enter
		……
	Monitor.Exit
}
finally
{
	……
}

参考:https://blog.csdn.net/wodownload2/article/details/119170818

但是如果在Enter和Exit直接报错或者出现exception的话,会直接卡死主线程。示例代码:

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

public class UseQueueInMultiThread : MonoBehaviour
{
    private int count = 0;
    private Queue<string> m_queue = new Queue<string>();
    private static readonly object m_lock = new object();
    private Thread t1;
    private Thread t2;
    private List<int> m_list = new List<int>();
    void Start()
    {
        t1 = new Thread(SayHello);
        //t2 = new Thread(SayNihao);
        t1.Name = "t1";
        //t2.Name = "t2";
        t1.Start("xiaoming");
        //t2.Start(123);
    }

    private void SayHello(object param)
    {
        for (int i = 0; i < 300; ++i)
        {
            //lock (m_lock) //如果未加锁,则导致最后输出的m_queue.count不等于总共的600
            //try
            {
                Monitor.Enter(m_lock);
                m_queue.Enqueue(i + "   " + param.ToString());
                Debug.LogError("SayHello " + i);
                if (i == 100)
                {
                    //throw new System.Exception("xxxxxxxxxxx");
                    Debug.LogError("dd = " + m_list[1]); //,如果使用Monitor,且不try catch,当数组越界会卡死主线程
                }
                Debug.LogError("yyyyyyyyyyyy");
            }
            //catch (Exception ex)
            //{
            //    Debug.LogError("ddaaaaaaaa " + ex.ToString());
            //}
            Monitor.Exit(m_lock);
            Thread.Sleep(10);
        }
    }

    private void SayNihao(object param)
    {
        for (int i = 0; i < 300; ++i)
        {
            //lock (m_lock) //如果未加锁,则导致最后输出的m_queue.count不等于总共的600
            Monitor.Enter(m_lock);
            {
                m_queue.Enqueue(i + "   " + param.ToString());
                Debug.LogError("SayNihao " + i);
            }
            Monitor.Exit(m_lock);
            Thread.Sleep(10);
        }
    }

    void Update()
    {
        count++;
        if (count % 10 == 0)
        {
            Debug.LogError("queue count=" + m_queue.Count + "  " + t1.ThreadState + "  " + t2.ThreadState);
            count = 0;
        }

        Monitor.Enter(m_lock); //这里在等待m_lock锁
        Debug.LogError("xxxxxxxxxxaaaaaaaaaaaa");
        Monitor.Exit(m_lock);
    }
}

上面的在t1线程中,执行SayHello的时候,数组越界。同时没有在Monitor.Enter和Monitor.Exit之间进行try catch,所以当发生崩溃的时候无法执行Monitor.Exit,此时子线程t1,未释放m_lock。

而主线程在Update中,等待m_lock,所以此时主线程卡死。

解决的方法有两个:

1、使用try catch,保证在Monitor.Enter和Monitor.Exit执行的代码,当发生崩溃、报错的时候,能执行Monitor.Exit,保证锁的释放。

2、或者采样lock(m_lock)的方式,不使用Monitor.Enter和Monitor.Exit方式,也是可以避免崩溃的。

卡死主线程的例子:

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

public class MultiThread : MonoBehaviour
{
    private static readonly object m_lock = new object();
    private Thread t1;
    private Thread t2;
    private int a = 1;
    private GameObject m_go;
    private float m_preTime;
    void Start()
    {
        Debug.LogError("Start");
        t1 = new Thread(SayHello);
        t1.Name = "t1";
        t1.Start("xiaoming");

        t2 = new Thread(Log);
        t2.Name = "t2";
        t2.Start();
    }

    private void Log()
    {
        while (true)
        {
            Debug.LogError("sub thead2xxxxxxxxxxx");
            Thread.Sleep(1000);
        }
    }

    private void SayHello(object param)
    {
        while(true)
        {
            Monitor.Enter(m_lock);
            Debug.LogError("sub thead");
            a++;
            if (a == 15)
            {
                Debug.LogError("sub thread " + a);
                StudioEventEmitter studioEventEmitter = m_go.GetComponent<StudioEventEmitter>();
            }
            Monitor.Exit(m_lock);
            Thread.Sleep(1000);
        }
    }

    void Update()
    {
        Monitor.Enter(m_lock);
        Debug.LogError("main thead");
        Monitor.Exit(m_lock);
        Thread.Sleep(1000);
    }

    private void OnDestroy()
    {
        t1.Abort();
        t2.Abort();
    }

    public void OnStop()
    {
        Debug.LogError("stopxaaa");
    }
}


在这里插入图片描述

主线程卡死了,子线程t2还可以继续执行。



版权声明:本文为wodownload2原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。