多线程与网络编程
网络编程
多线程
概述
单任务处理:一个任务完成后才能进行下一个任务
多任务处理:CPU分时操作,每个任务看似同时运行;实则通过时间片分时进行;因为时间间隔过小,察觉不出。
进程
应用程序的一个运行实例,包含程序所需资源的内存区域,是操作系统进行资源分配的单元,进程隔离了正在执行的不同程序。
优点:进程之间相互独立,互不影响。
线程
进程中的一个执行单元(进程的程序边界,要靠线程执行程序,线程执行方法,执行完毕释放线程),是CPU分配时间片的单位,一个进程可以包含多个线程,且相互独立,共享当前进程的所有资源。
优点:
- 并发执行,合理使用CPU资源
- 相同程序的线程共享堆内存
缺点:
- 频繁创建/销毁线程增加性能开销。解决:线程池(ThreadPool)
- 访问共享资源可能造成冲突。解决:线程同步:lock;共享读,独占写
- 辅助线程不能访问Unity API。解决:线程交叉访问助手类,利用委托间接访问API 注意事项:
- Unity的API(涉及脚本生命周期)不能在辅助线程运行
- Unity定义的基本结构(int,Vector3,Quaternion等)可以在辅助线程计算
- Unity定义的基本类型的函数可以在分线程运行
调度脚本生命周期的线程称之为“主线程”
自行创建的线程称之为“子线程/辅线程”
多线程
在单核系统的一个单位时间内,CPU只能运行单个线程,运行顺序取决于线程的优先级。如果在单位时间(一个时间片)内线程未能完成执行,系统就会把线程的状态信息保存到本地存储器(TLS)中,以便下次执行时恢复执行。因为切换频密,所以多线程可被视作同时进行,而实际只是一个假象。
在多核系统的一个单位时间内,进程或线程可以在不同CPU中运行,使得并行处理真正执行
适用性
耗时的任务,通过多线程可以并行处理 一个程序完成多个任务,通过多个线程使用多核CPU来处理,可以提升性能。
图示:
多线程实现
命名空间:System.Threading
Thread类(线程)
-
创建线程:创建Thread类的一个对象,分配一个线程工作方法。
Thread thread = new Thread(工作方法);
-
启动线程:Start方法
thread .Start();
-
终止线程:工作方法自然退出,线程终止。
thread .Abort();
-
线程睡眠:Thread.Sleep(1000);//睡眠1秒
-
线程暂停:信号灯:ManualResetEvent
- 创建信号灯:ManualResetEvent signal = new ManualResetEvent(false);
- “等一下“:signal.WaitOne();
- “红灯/绿灯“:signal.Reset();//进程停止;signal.Set();//进程开始
ThreadPool类(线程池)
在频繁创建和销毁线程时使用线程池技术,可以有效减少时间以及系统资源的开销。
ThreadPool.QueueUserWorkItem(工作方法);
前/后台线程
属性:IsBackground设置线程是否是后台线程
前台线程:程序必须等待所有前台线程结束后才能退出。【Thread创建的线程默认值为前台线程】
后台线程:程序不考虑后台线程,后台线程随程序退出而结束。【ThreadPool创建的线程默认值为后台线程】
备注:Unity程序退出后,前台线程也随即关闭。
线程状态
未启动状态Unstarted:创建线程对象
运行状态Running:执行绑定的方法
等待睡眠阻塞状态WaitSleepJoin:暂时停止执行,资源交给其他线程使用
终止状态Stopped:线程销毁
线程同步
需要同步的原因:
多个线程同一时刻访问共享资源(线程共享实例变量,静态变量),由于每个线程都不知道其他线程的操作,结果将产生不可预知的数据损坏。
同步:线程之间相互等待排队执行。
如何同步:
将需要同步的代码用关键字:lock锁定,锁定后该代码对于线程来讲就是独占使用的。当其他线程试图进入被锁定的临界区时,只能等待解锁后才可访问。因此锁定代码时是排队访问的,所以叫线程同步。
Lock锁原理: lock(引用类型)
引用类型对象在堆中的分配:实例成员,同步块索引(默认索引为-1),类型指针(指向类型对象)。
对引用类型对象上锁后,同步块索引会指向同步块数组中的一个对象;改变默认索引。
当其他对象执行lock的时候会等待该引用类型对象同步块索引设置为-1后执行脚本。
同步块索引:标识作用。
其他同步方法:共享读 独占写
协程(协同程序)与线程
协程
- 执行在主线程中,每帧轮询检测执行条件;不是线程,可以访问Unity API。
- 通过MonoBehaviour的StartCoroutine方法开启,执行到yield return暂时跳出该方法;等待yield return后的语句执行完成后继续执行方法中剩下的语句。
- 共享堆,不共享栈,访问共享数据,不存在并发冲突。
- 开启/结束协程的性能开销小于线程。
- 协程的本质是迭代器(IEnumerator)拥有MoveNext()方法和Current属性(表示当前值),
线程
- 为防止辅助线程破坏脚本生命周期明确的执行顺序,Unity拒绝辅助线程访问主线程API。
- 通过Thread的Start方法开启,由操作系统抢占调度,到达分配时间暂停。执行时间不可预计。
- 共享堆,不共享栈,访问共享数据,存在并发冲突。
- 由于线程具备内核对象,环境块,DLL线程连接和线程分离通知,用户模式栈,内核模式栈等元素,所以开启/停止消耗的性能较大。
Socket通讯
概述
Socket英语直译为“插座”,计算机术语为“套接字”。
网络上的两个程序通过一个双向的通信连接实现数据的交换,Socket表示网络通信终结点,有三个重要参数:IP,端口,协议。
IP:本地主机在网络中的地址,两个程序要通讯,首先要知道对方的位置,即对方的IP地址
端口:用于识别进程,同一本地主机上各程序的端口不能相同
协议:计算机网络中进行数据交换而建立的规则,标准。
UDP
User Data Protocol 用户数据报协议
概述
UDP是不连接的数据报模式。即传输数据之前源端和终端不建立连接。使用尽最大努力交付原则,即不保证可靠交付。
数据报模式:由于不建立连接,收到的数据可能是任意主机发送的,所以接收端读次数必须与发送端写次数相同(即Receive和Send次数相同),每次只接收一个报文,避免多个报文合并。但是如果报文过长,多出部分会被丢弃,所以注意数据最大为1472字节。
报文结构
UDP包头短,只有8个字节。相当于20个字节的TCP信息包开销更小。
实现步骤
TCP
Transmission Control Protocol 传输控制协议
概述
TCP是面向连接的流模式。即传输数据之前源端和终端建立可靠的连接,保证数据传输的正确性。
流模式:由于建立连接,收到的数据都是同一主机发送的,所以可以发送写一次,接收端读多次;也可以发送端写多次,接收端读多次。但每次传输数据最大为1460字节。
报文结构
三次握手
所谓三次握手就是建立TCP连接的过程,需要客户端和服务端总共发送3个包确认连接成功。在Socket编程中,这一过程由客户端执行Connect来触发。
简而言之:
第一次:客户端向服务端发出连接请求数据包。“我想跟你聊天,可以么?”
第二次:服务端向客户端发送同意连接和要求同步的数据包。“可以呀,什么时候?”
第二次:客户端再发出一个数据包确认服务端的同步要求。“就现在。”
四次挥手
所谓四次挥手就是终止TCP连接的过程,需要客户端和服务端总共发送4个包以确认连接断开。在Socket编程中,这一过程由客户端或服务端任一方执行close来触发。
简而言之:
第一次:客户端向服务端发送断开请求数据包。“今天就聊到这吧”
第二次:服务端向客户端发送同意断开数据包。“嗯,好的”
第三次:服务端再向客户端发送断开请求数据包。“那我挂了呀?”
第四次:客户端再向服务端发送确认断开数据包。“拜拜”
此时服务端断开连接,客户端过会发现服务端没有回复,也断开连接。
实现步骤
UDP与TCP的区别
- 基于连接与无连接;
- 对系统资源的要求(TCP较多,UDP较少);
- UDP程序结构较简单;
- 数据报模式与流模式;
- TCP保证数据正确性,UDP可能丢包,TCP保证数据顺序,UDP不保证。