一个有趣的Unity多线程练习例子

Posted stalendp

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一个有趣的Unity多线程练习例子相关的知识,希望对你有一定的参考价值。

这里将记录一些C#线程相关的信息,将持续更新,最总将形成一个工具类,方便在Unity中使用。

一些Tips:

1)Monitor VS. Mutex  

Difference between mutex and monitor. 

The biggest difference between the two is the scope: a mutex's scope is system-wide, whilst monitor's scope depends on the scope of the object you lock on. This means that with monitor, the widest scope you can get is generally application-wide.

So you would mostly use monitor to synchronise between threads running in an application, and use mutex to synchronise between different applications.

2)Monitor VS. lock

lock is just shortcut for Monitor.Enter with try + finally and Monitor.Exit. Use lock statement whenever it is enough - if you need something like TryEnter, you will have to use Monitor.

3)Semaphore VS. Monitors

Semaphore.WaitOne/Release vs Monitor.Pulse/Wait

Monitor.Wait and Monitor.Pulse are fundamental synchronization mechanisms that can be used to form pretty much any other synchronization device including semaphores.

结论,使用Monitor能够达到多线程的目的,lock可以简化这个过程。

4)Terminating a thread cleanly

5) Should a return statement be inside or outside a lock?

 At the IL level they are identical。

Only the current owner of the lock can signal a waiting object using Pulse.

The Pulse, PulseAll, and Wait methods must be invoked from within a synchronized block of code.

一个下载线程的例子:

 private class DownloadFactory 
        #region ===== 线程共享资源 =====
        private volatile bool isOpen;
        private Queue<DownloadJob> workingJobs = new Queue<DownloadJob>();
        private Queue<DownloadJob> finishedJobs = new Queue<DownloadJob>();
        #endregion

        const int WORK_NUM = 5; // 开启5个下载线程
        private Thread[] workers; // 工作线程,负责下载
        private Thread checker;  // 检测线程, 用来写磁盘或则重新下载

        public DownloadFactory() 
            isOpen = false;

            workers = new Thread[WORK_NUM];
            for (int i = 0; i < WORK_NUM; i++)  
                workers[i] = new Thread(() => 
                    while (isOpen) 
                        var job = takeJob();
                        // do download affairs!
                        finJob(job);
                    
                );
            

            checker = new Thread(() => 
                while (isOpen) 
                    var job = checkJob();
                    if (!job.isOK)   // 下载不成功,则重新下载
                        putJob(job);
                     else  // 写文件操作

                    
                
            );
        

        public bool isDone 
            get 
                return totalCount == 0;
            
        

        // 开始
        public void Open(Queue<DownloadJob> _allJobs) 
            workingJobs = _allJobs;
            isOpen = true;
            checker.Start();
            foreach (var w in workers) 
                w.Start();
            
        

        // 结束
        public void Close() 
            isOpen = false;
            checker.Interrupt();
            foreach (var w in workers) 
                w.Interrupt();
            
            checker.Join();
            foreach (var w in workers) 
                w.Join();
            
        

        #region ========= 线程同步逻辑 ===========
        private int totalCount 
            get 
                lock (this) 
                    return workingJobs.Count + finishedJobs.Count;
                
            
        

        private DownloadJob takeJob() 
            lock (this) 
                while (workingJobs.Count <= 0) 
                    Monitor.Wait(this);
                
                var rt = workingJobs.Dequeue();
                Monitor.PulseAll(this);
                return rt;
            
        

        private void putJob(DownloadJob job) 
            lock (this) 
                workingJobs.Enqueue(job);
                Monitor.PulseAll(this);
            
        

        private void finJob(DownloadJob job) 
            lock (this) 
                finishedJobs.Enqueue(job);
                Monitor.PulseAll(this);
            
        

        private DownloadJob checkJob() 
            lock(this) 
                while (finishedJobs.Count <= 0) 
                    Monitor.Wait(this);
                
                var job = finishedJobs.Dequeue();
                Monitor.PulseAll(this);
                return job;
            
        
        #endregion
    

例子:生产消费者模式

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

public class MultiThread : MonoBehaviour 
    private List<ThreadHelper> threads = new List<ThreadHelper>();
    public Text txtMessage;
    public Text txtMsg2;

    void Start() 
        var kitchen = new Kitchen();
        var table = new Table(10);
        var msgHandler = new TMsgHandler(this, msg => 
            txtMsg2.text = msg as string;
        );

        ThreadHelper.Run eater = env => 
            try 
                var random = new System.Random((int)env.param);
                while (env.alive) 
                    var cake = table.take();
                    Thread.Sleep(random.Next(100));
                    msgHandler.send(delegate 
                        txtMessage.text = env.name + "" + cake;
                    );
                
             catch (ThreadInterruptedException ex) 
                msgHandler.send(delegate 
                    txtMessage.text = string.Format("The 0 Interrupted!", env.name);
                );
             finally 
                msgHandler.send(delegate 
                    txtMessage.text = string.Format("The 0 Ended!", env.name);
                );
            
        ;

        ThreadHelper.Run maker = env => 
            try 
                var param = env.param as object[];
                var random = new System.Random((int)param[1]);
                while (env.alive) 
                    Thread.Sleep(random.Next(100));
                    var cake = string.Format("[ Cake No.0 by 1 ]", kitchen.nextCakeId, env.name);
                    table.put(cake);
                    msgHandler.send(env.name + "" + cake);
                

             catch (ThreadInterruptedException ex) 
                msgHandler.send(delegate 
                    txtMessage.text = string.Format("The 0 Interrupted!", env.name);
                );
             finally 
                msgHandler.send(delegate 
                    txtMessage.text = string.Format("The 0 Ended!", env.name);
                );
            
        ;
        // 一些随机数种子:13821, 97535, 79593, 87481, 66158, 18199, 40915, 63093, 77880, 93602, 17603, 38327  
        threads.Add(new ThreadHelper("MakerThread-1", maker, new object[]  "111", 13821 ));
        threads.Add(new ThreadHelper("MakerThread-2", maker, new object[]  "222", 97535 ));
        threads.Add(new ThreadHelper("MakerThread-3", maker, new object[]  "111", 79593 ));
        threads.Add(new ThreadHelper("MakerThread-4", maker, new object[]  "222", 87481 ));
        threads.Add(new ThreadHelper("MakerThread-5", maker, new object[]  "111", 66158 ));
        threads.Add(new ThreadHelper("MakerThread-6", maker, new object[]  "222", 18199 ));
        threads.Add(new ThreadHelper("EaterThread-A", eater, 40915));
        threads.Add(new ThreadHelper("EaterThread-B", eater, 63093));
        threads.Add(new ThreadHelper("EaterThread-C", eater, 77880));
        threads.Add(new ThreadHelper("EaterThread-D", eater, 93602));
        threads.Add(new ThreadHelper("EaterThread-E", eater, 17603));
        threads.Add(new ThreadHelper("EaterThread-F", eater, 38327));

        // 开始线程    
        threads.ForEach(t => t.Start());
    

    void OnDestroy() 
        threads.ForEach(m => m.Kill());
    

    // 同步消息到主线程,模拟android.os.Handler的概念  
    public class TMsgHandler 
        public delegate void PostHandler();

        private System.Action<object> msgHandler;
        private List<object> messages = new List<object>();

        public TMsgHandler(MonoBehaviour mono, System.Action<object> handler = null) 
            msgHandler = handler;
            mono.StartCoroutine(checkMessage());
        

        public void send(PostHandler action) 
            lock (this) 
                messages.Add(action);
            
        

        public void send(object param) 
            lock (this) 
                messages.Add(param);
            
        

        private List<object> getMessages() 
            lock (this) 
                if (messages.Count > 0) 
                    var old = messages;
                    messages = new List<object>();
                    return old;
                
                return null;
            
        

        private IEnumerator checkMessage() 
            while (true) 
                yield return null;
                var msgs = getMessages();
                if (msgs != null) 
                    Debug.LogError(msgs.Count);
                    foreach (var m in msgs) 
                        if (m is PostHandler) 
                            var h = m as PostHandler;
                            h();
                         else 
                            if (msgHandler != null) 
                                msgHandler(m);
                            
                        
                    
                
            
        
    

    public class ThreadHelper 
        public delegate void Run(ThreadHelper env);
        public Run run = null;
        public volatile bool alive;

        public string name  get  return thread.Name;  
        public Thread thread  get; private set; 
        public object param = null;
        public ThreadHelper(string name, Run _run, object _param = null) 
            alive = false;
            run = _run;
            param = _param;
            thread = new Thread(_inner_run);
            thread.Name = name;
            //background threads do not prevent a process from terminating.     
            //Once all foreground threads belonging to a process have terminated,    
            //the common language runtime ends the process.    
            thread.IsBackground = true;
        

        public void _inner_run() 
            if (run != null) 
                run(this);
            
        

        public void Start() 
            alive = true;
            thread.Start();
        

        public void notifyStop() 
            alive = false;
        

        public void Kill(int timeout = 0) 
            thread.Interrupt();
            thread.Join(timeout);
        
    

    public class Kitchen 
        private int CakeId;

        public int nextCakeId 
            get 
                lock (this) 
                    return CakeId++;
                
            
        
    

    public class Table 
        private string[] buffer;
        private int tail;
        private int head;
        private int count;

        public Table(int _count) 
            this.buffer = new string[_count];
            tail = head = count = 0;
        

        public void put(string cake) 
            lock (this) 
                //Debug.LogWarningFormat("0 >> puts >> 1", Thread.CurrentThread.Name, cake);
                while (count >= buffer.Length) 
                    //Debug.LogFormat("0 wait BEGIN", Thread.CurrentThread.Name);
                    Monitor.Wait(this);
                    //Debug.LogFormat("0 wait END", Thread.CurrentThread.Name);
                
                buffer[tail] = cake;
                tail = (tail + 1) % buffer.Length;
                count++;
                Monitor.Pulse(this);
            
        

        public string take() 
            lock (this) 
                while (count <= 0) 
                    //Debug.LogFormat("0 wait BEGIN", Thread.CurrentThread.Name);
                    Monitor.Wait(this);
                    //Debug.LogFormat("0 wait END", Thread.CurrentThread.Name);
                

                var cake = buffer[head];
                head = (head + 1) % buffer.Length;
                count--;
                Monitor.Pulse(this);
                //Debug.LogErrorFormat("0 << takes << 1", Thread.CurrentThread.Name, cake);
                return cake;
            
        
    

 

更加有趣的一个版本:

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

// 一个线程辅助类
public class ThreadObj 
    protected System.Action<ThreadObj> run = null;
    public volatile bool alive;

    public string thdName  get  return thread.Name;  
    public Thread thread  get; private set; 
    public object param = null;
    public ThreadObj(string name, System.Action<ThreadObj> _run = null, object _param = null) 
        alive = false;
        run = _run;
        param = _param;
        thread = new Thread(_inner_run);
        thread.Name = name;
        //background threads do not prevent a process from terminating.     
        //Once all foreground threads belonging to a process have terminated,    
        //the common language runtime ends the process.    
        thread.IsBackground = true;
    

    public void _inner_run() 
        if (run != null) 
            run(this);
        
    

    public void Start() 
        alive = true;
        thread.Start();
    

    public void notifyStop() 
        alive = false;
    

    public void Kill(int timeout = 0) 
        thread.Interrupt();
        thread.Join(timeout);
    


// 同步消息到主线程,模拟android.os.Handler的概念  
public class TMsgHandler 
    public delegate void PostHandler();

    private System.Action<object> msgHandler;
    private List<object> messages = new List<object>();

    public TMsgHandler(MonoBehaviour mono, System.Action<object> handler = null) 
        msgHandler = handler;
        mono.StartCoroutine(checkMessage());
    

    public void send(object param) 
        lock (this) 
            messages.Add(param);
        
    

    private List<object> getMessages() 
        lock (this) 
            if (messages.Count > 0) 
                var old = messages;
                messages = new List<object>();
                return old;
            
            return null;
        
    

    private IEnumerator checkMessage() 
        while (true) 
            yield return null;
            var msgs = getMessages();
            if (msgs != null) 
                foreach (var m in msgs) 
                    if (m is PostHandler) 
                        var h = m as PostHandler;
                        h();
                     else 
                        if (msgHandler != null) 
                            msgHandler(m);
                        
                    
                
            
        
    

using UnityEngine;
using UnityEngine.UI;

// 餐厅表现
public class UIDiningHall : MonoBehaviour 
    public Text cakeNo;
    public Table table;
    public Transform makers;
    public Transform eaters;

    private DiningHall diningHall = new DiningHall();

    // Use this for initialization
    void Start () 
        var msgHandler = new TMsgHandler(this, msg => 
            if (msg is Maker.Event)   // 厨师消息
                Debug.Log(msg.ToString());
             else if (msg is Eater.Event)  // 食客消息
                Debug.Log(msg.ToString());
            
        );
        diningHall.Init(msgHandler);
        diningHall.Open();  // 餐厅营业
	

    void OnDestroy() 
        Debug.LogError("=========Dining Hall is Closing===========");
        diningHall.Close(); // 餐厅打烊
    
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.Threading;

// 餐厅, 逻辑运行
public class DiningHall 

    private List<ThreadObj> persons = new List<ThreadObj>(); // 餐厅中的人,包括厨师和食客

    public Kitchen kitchen  get; private set; 
    public Table table  get; private set; 
    private TMsgHandler handler;

    public void Init(TMsgHandler msgHandler) 
        handler = msgHandler;
        kitchen = new Kitchen(30);
        table = new Table(5);

        // 一些随机数种子:13821, 97535, 79593, 87481, 66158, 18199, 
        // 40915, 63093, 77880, 93602, 17603, 38327

        // 创建一些厨师
        persons.Add(new Maker("CookA", 1, 13821, this));
        persons.Add(new Maker("CookB", 1, 97535, this));
        persons.Add(new Maker("CookC", 1, 79593, this));
        persons.Add(new Maker("CookD", 1, 87481, this));
        persons.Add(new Maker("CookE", 1, 87481, this));
        persons.Add(new Maker("CookF", 1, 87481, this));
        persons.Add(new Maker("CookG", 1, 87481, this));
        persons.Add(new Maker("CookA", 1, 13821, this));

        // 创建一些食客
        persons.Add(new Eater("Eater1", 10, 40915, this));
        persons.Add(new Eater("Eater2", 20, 63093, this));
        persons.Add(new Eater("Eater3", 30, 77880, this));
        persons.Add(new Eater("Eater4", 40, 93602, this));
        persons.Add(new Eater("Eater1", 10, 40915, this));
        persons.Add(new Eater("Eater2", 20, 63093, this));
        persons.Add(new Eater("Eater3", 30, 77880, this));
        persons.Add(new Eater("Eater4", 40, 93602, this));;
    

    // 发送信息到UI
    public void send2UI(object param) 
        handler.send(param);
    

    public Cake takeCake() 
        return table.take();
    

    // 开始营业
    public void Open() 
        // 开始线程, 让餐厅运作起来    
        persons.ForEach(t => t.Start());
    

    // 结束营业
    public void Close() 
        persons.ForEach(t => t.Kill());
    

   


class Eater : ThreadObj 
    public string name  get; private set;  // 食客的名字
    public int speed  get; private set;  // 食客吃蛋糕的速度
    private System.Random rand;
    private DiningHall dh;

    public Eater(string name, int speed, int seed, DiningHall dh) : base(name) 
        this.name = name;
        this.speed = speed;
        this.rand = new System.Random(seed);
        this.dh = dh;
        this.run = Run;
    

    // 食客在餐厅中取餐并吃~~
    private void Run(ThreadObj env) 
        try 
            while (env.alive) 
                // 获取蛋糕
                Event.takeStart(this);
                var cake = dh.takeCake();

                // 开始吃蛋糕, 直至吃完
                while (cake.percent > 0) 
                    cake.eat(rand.Next(speed)); // 计算一口的大小
                    Event.eatStart(this, cake);
                    Thread.Sleep(100); // 每吃一口,要咀嚼一下
                
                Debug.LogWarning("[Eater] " + cake.ToString());
                // 蛋糕吃完了
                Event.eatEnd(this);
            
         catch (ThreadInterruptedException ex) 
         finally 
        
    

    // 食客消息, 和UI通信的消息
    public class Event 
        public int id  get; private set; 
        public int type  get; private set; 
        public Cake cake  get; private set; 

        public static void takeStart(Eater eater) 
            eater.dh.send2UI(new Event()  type = 0 );
        

        public static void eatStart(Eater eater, Cake _cake) 
            eater.dh.send2UI(new Event()  type = 1, cake = _cake.clone() );
        

        public static void eatEnd(Eater eater) 
            eater.dh.send2UI(new Event()  type = 2 );
        

        public override string ToString() 
            return string.Format("[Eater] type: 0, cake: 1", type, cake == null ? "-" : cake.ToString());
        
    


// 厨师
public class Maker : ThreadObj 
    public string name  get; private set;  // 厨师的名字
    public float speed  get; private set;  // 厨师制作蛋糕的速度
    private System.Random rand;
    private DiningHall dh;

    public Maker(string name, float speed, int seed, DiningHall dh) : base(name) 
        this.name = name;
        this.speed = speed;
        this.rand = new System.Random(seed);
        this.dh = dh;
        this.run = Run;
    

    // 厨师工作中~~
    private void Run(ThreadObj env) 
        try 
            while (env.alive) 
                // 0. 获取蛋糕机的使用
                Event.waitMachine(this);
                var machine = dh.kitchen.getMachine(); // 获取蛋糕机

                // 1. 设计一个蛋糕,并让面包机做
                //dh.send2UI(Event.makeStart(cake)); // 开始制作
                var type = rand.Next(5);
                var amount = rand.Next(50, 200);
                var cake = new Cake(type, amount);
                machine.Cook(cake); // 制作蛋糕中~~

                Event.waitCooking(this, cake);
                cake = machine.GetCake(); //得到蛋糕 

                // 2. 放置蛋糕
                Event.putCake(this);
                dh.table.put(cake);

                Event.putCakeEnd(this);
            
         catch (ThreadInterruptedException ex) 
         finally 
        
    

    public class Event 
        public int type  get; private set; 
        public Cake cake  get; private set; 

        public static void waitMachine(Maker m) 
            m.dh.send2UI(new Event()  type = 0 );
        

        public static void waitCooking(Maker m, Cake c)  // 等待
            m.dh.send2UI(new Event()  type = 1, cake = c.clone() );
        

        public static void putCake(Maker m) 
            m.dh.send2UI(new Event()  type = 2 );
        

        public static void putCakeEnd(Maker m) 
            m.dh.send2UI(new Event()  type = 3 );
        

        public override string ToString() 
            return string.Format("[Maker] type: 0, cake: 1", type, cake==null ? "-" : cake.ToString());
        
    


// 蛋糕
public class Cake 
    static int ID = 0;
    private int _id = 0;
    public int type  get; private set;  // 蛋糕类型
    public float totalAmount  get; private set;  // 蛋糕的设计尺寸
    public float curAmount  get; private set;  // 当前蛋糕的尺寸 
    private int status;  // 0 表示制作中, 1表示制作完成; -1 表示状态不可变(由于UI显示)
    
    public float percent  get  return curAmount / (float)totalAmount;  

    public Cake(int type, float totalAmount, int id=-1, float curAmount =0, int status = 0) 
        this.type = type;
        this.totalAmount = totalAmount;
        this.curAmount = curAmount;
        this.status = status;
        _id = id < 0 ? ++ID : id;
    

    public int id 
        get 
            return _id;
        
    

    // 制作蛋糕
    public void make(float amount) 
        if (status == 0) 
            curAmount += amount;
            if (curAmount >= totalAmount) 
                curAmount = totalAmount;
                status = 1;
            
        
    

    //  吃蛋糕
    public void eat(int amount) 
        if (status == 1) 
            curAmount -= amount;
            if (curAmount <= 0) 
                curAmount = 0;
                status = -1;
            
        
    

    public Cake clone() 
        return new Cake(type, totalAmount, id, curAmount, status) ;
    

    public override string ToString() 
        return string.Format(" id: 0, type: 1, total: 2:0.0, cur: 3:0.0, status: 4, percent: 5:0.0 ", 
            id, type, totalAmount, curAmount, status, percent);
    



// 蛋糕机
public class CakeMachine 
    public Kitchen kitchen  get; private set; 
    private Cake curCake = null;
    private bool isCooking = false; 

    public CakeMachine(string name, Kitchen kitchen)  
        this.kitchen = kitchen;
    

    private void Run() 
        if (curCake != null) 
            while (curCake.percent < 1) 
                curCake.make(10);
                Thread.Sleep(100); // 制作时间
            
            Debug.LogError("Done!" + curCake.id );
            MarkAsDone();
         
    

    // 开始做蛋糕
    public void Cook(Cake cake) 
        lock (this) 
            while (isCooking) 
                Monitor.Wait(this);
            
            curCake = cake;
            isCooking = true;
            Debug.LogWarning("[Cook] " + curCake.ToString());
            var thread = new Thread(Run);
            thread.IsBackground = true;
            thread.Start();
        
    

    // 拿出蛋糕
    public Cake GetCake() 
        lock(this) 
            while (isCooking) 
                Monitor.Wait(this);
            
            return curCake.clone();
        
    

    // 手动结束
    public void ShutDown() 
        lock (this) 
            //Kill();
            MarkAsDone();
        
    

    // 蛋糕做好了
    public void MarkAsDone() 
        lock (this) 
            isCooking = false;
            Monitor.PulseAll(this);
            kitchen.MachineReady();
        
    

    public bool isDone 
        get 
            lock (this) 
                return !isCooking;
            
        
    


// 厨房, 里面有蛋糕机
public class Kitchen 
    private List<CakeMachine> machines;  // 蛋糕机
    public Kitchen(int machineNum) 
        machines = new List<CakeMachine>();
        for (int i = 0; i < machineNum; i++) 
            machines.Add(new CakeMachine("M" + i, this));
        
    

    public CakeMachine getMachine()  // 尝试获取一台可用的机器
        lock (this) 
            CakeMachine free = null;
            while (free == null) 
                free = machines.Find(m => m.isDone);
                if (free == null)  // 没有机器是空闲的,需要等待
                    Monitor.Wait(this);
                
            
            return free;
        
    

    public void MachineReady()   // 某台机器用好了
        lock (this) 
            Monitor.PulseAll(this);
        
    


// 定义桌子,注意这个是共享资源!!厨师和食客共享
public class Table 
    private Cake[] buffer;
    private int tail;
    private int head;
    private int count;

    public Table(int _count) 
        this.buffer = new Cake[_count];
        tail = head = count = 0;
    

    public void put(Cake cake) 
        lock (this) 
            while (count >= buffer.Length) 
                Monitor.Wait(this);
            
            buffer[tail] = cake;
            tail = (tail + 1) % buffer.Length;
            count++;
            Monitor.Pulse(this);
        
    

    public Cake take() 
        lock (this) 
            while (count <= 0) 
                Monitor.Wait(this);
            

            var cake = buffer[head];
            head = (head + 1) % buffer.Length;
            count--;
            Monitor.Pulse(this);
            return cake;
        
    

 

 

 

 

以上是关于一个有趣的Unity多线程练习例子的主要内容,如果未能解决你的问题,请参考以下文章

看了这个有趣的例子,你就能秒懂Java中的多线程同步了!

多线程和蕃茄炒蛋

java多线程练习题

多线程编程模型:Pipeline模式 上

多线程常见锁的策略

Linux下基于TCP协议的群聊系统设计(多线程+select)