多线程与网络编程

网络编程

多线程

概述

单任务处理:一个任务完成后才能进行下一个任务

多任务处理:CPU分时操作,每个任务看似同时运行;实则通过时间片分时进行;因为时间间隔过小,察觉不出。

进程

应用程序的一个运行实例,包含程序所需资源的内存区域,是操作系统进行资源分配的单元,进程隔离了正在执行的不同程序。

优点:进程之间相互独立,互不影响。

线程

进程中的一个执行单元(进程的程序边界,要靠线程执行程序,线程执行方法,执行完毕释放线程),是CPU分配时间片的单位,一个进程可以包含多个线程,且相互独立,共享当前进程的所有资源。

优点:

  1. 并发执行,合理使用CPU资源
  2. 相同程序的线程共享堆内存

缺点:

  1. 频繁创建/销毁线程增加性能开销。解决:线程池(ThreadPool)
  2. 访问共享资源可能造成冲突。解决:线程同步:lock;共享读,独占写
  3. 辅助线程不能访问Unity API。解决:线程交叉访问助手类,利用委托间接访问API 注意事项:
  4. Unity的API(涉及脚本生命周期)不能在辅助线程运行
  5. Unity定义的基本结构(int,Vector3,Quaternion等)可以在辅助线程计算
  6. Unity定义的基本类型的函数可以在分线程运行

调度脚本生命周期的线程称之为“主线程”

自行创建的线程称之为“子线程/辅线程”

多线程

在单核系统的一个单位时间内,CPU只能运行单个线程,运行顺序取决于线程的优先级。如果在单位时间(一个时间片)内线程未能完成执行,系统就会把线程的状态信息保存到本地存储器(TLS)中,以便下次执行时恢复执行。因为切换频密,所以多线程可被视作同时进行,而实际只是一个假象。

在多核系统的一个单位时间内,进程或线程可以在不同CPU中运行,使得并行处理真正执行

适用性

耗时的任务,通过多线程可以并行处理 一个程序完成多个任务,通过多个线程使用多核CPU来处理,可以提升性能。

图示: alt

多线程实现

命名空间:System.Threading

Thread类(线程)

  1. 创建线程:创建Thread类的一个对象,分配一个线程工作方法。

    Thread thread = new Thread(工作方法);

  2. 启动线程:Start方法

    thread .Start();

  3. 终止线程:工作方法自然退出,线程终止。

    thread .Abort();

  4. 线程睡眠:Thread.Sleep(1000);//睡眠1秒

  5. 线程暂停:信号灯:ManualResetEvent

    1. 创建信号灯:ManualResetEvent signal = new ManualResetEvent(false);
    2. “等一下“:signal.WaitOne();
    3. “红灯/绿灯“:signal.Reset();//进程停止;signal.Set();//进程开始

ThreadPool类(线程池)

在频繁创建和销毁线程时使用线程池技术,可以有效减少时间以及系统资源的开销。

ThreadPool.QueueUserWorkItem(工作方法);

前/后台线程

属性:IsBackground设置线程是否是后台线程

前台线程:程序必须等待所有前台线程结束后才能退出。【Thread创建的线程默认值为前台线程】

后台线程:程序不考虑后台线程,后台线程随程序退出而结束。【ThreadPool创建的线程默认值为后台线程】

备注:Unity程序退出后,前台线程也随即关闭。

线程状态

未启动状态Unstarted:创建线程对象

运行状态Running:执行绑定的方法

等待睡眠阻塞状态WaitSleepJoin:暂时停止执行,资源交给其他线程使用

终止状态Stopped:线程销毁

alt

线程同步

需要同步的原因:

多个线程同一时刻访问共享资源(线程共享实例变量,静态变量),由于每个线程都不知道其他线程的操作,结果将产生不可预知的数据损坏。

同步:线程之间相互等待排队执行。

如何同步:

将需要同步的代码用关键字:lock锁定,锁定后该代码对于线程来讲就是独占使用的。当其他线程试图进入被锁定的临界区时,只能等待解锁后才可访问。因此锁定代码时是排队访问的,所以叫线程同步。

Lock锁原理: lock(引用类型)

引用类型对象在堆中的分配:实例成员,同步块索引(默认索引为-1),类型指针(指向类型对象)。

对引用类型对象上锁后,同步块索引会指向同步块数组中的一个对象;改变默认索引。

当其他对象执行lock的时候会等待该引用类型对象同步块索引设置为-1后执行脚本。

同步块索引:标识作用。

其他同步方法:共享读 独占写

协程(协同程序)与线程

协程

  1. 执行在主线程中,每帧轮询检测执行条件;不是线程,可以访问Unity API。
  2. 通过MonoBehaviour的StartCoroutine方法开启,执行到yield return暂时跳出该方法;等待yield return后的语句执行完成后继续执行方法中剩下的语句。
  3. 共享堆,不共享栈,访问共享数据,不存在并发冲突。
  4. 开启/结束协程的性能开销小于线程。
  5. 协程的本质是迭代器(IEnumerator)拥有MoveNext()方法和Current属性(表示当前值),

线程

  1. 为防止辅助线程破坏脚本生命周期明确的执行顺序,Unity拒绝辅助线程访问主线程API。
  2. 通过Thread的Start方法开启,由操作系统抢占调度,到达分配时间暂停。执行时间不可预计。
  3. 共享堆,不共享栈,访问共享数据,存在并发冲突。
  4. 由于线程具备内核对象,环境块,DLL线程连接和线程分离通知,用户模式栈,内核模式栈等元素,所以开启/停止消耗的性能较大。

Socket通讯

概述

Socket英语直译为“插座”,计算机术语为“套接字”。

网络上的两个程序通过一个双向的通信连接实现数据的交换,Socket表示网络通信终结点,有三个重要参数:IP,端口,协议。

IP:本地主机在网络中的地址,两个程序要通讯,首先要知道对方的位置,即对方的IP地址

端口:用于识别进程,同一本地主机上各程序的端口不能相同

协议:计算机网络中进行数据交换而建立的规则,标准。

alt

UDP

User Data Protocol 用户数据报协议

概述

UDP是不连接的数据报模式。即传输数据之前源端和终端不建立连接。使用尽最大努力交付原则,即不保证可靠交付。

数据报模式:由于不建立连接,收到的数据可能是任意主机发送的,所以接收端读次数必须与发送端写次数相同(即Receive和Send次数相同),每次只接收一个报文,避免多个报文合并。但是如果报文过长,多出部分会被丢弃,所以注意数据最大为1472字节。

报文结构

UDP包头短,只有8个字节。相当于20个字节的TCP信息包开销更小。

alt

实现步骤

alt

TCP

Transmission Control Protocol 传输控制协议

概述

TCP是面向连接的流模式。即传输数据之前源端和终端建立可靠的连接,保证数据传输的正确性。

流模式:由于建立连接,收到的数据都是同一主机发送的,所以可以发送写一次,接收端读多次;也可以发送端写多次,接收端读多次。但每次传输数据最大为1460字节。

报文结构

alt

三次握手

所谓三次握手就是建立TCP连接的过程,需要客户端和服务端总共发送3个包确认连接成功。在Socket编程中,这一过程由客户端执行Connect来触发。

简而言之:

第一次:客户端向服务端发出连接请求数据包。“我想跟你聊天,可以么?”

第二次:服务端向客户端发送同意连接和要求同步的数据包。“可以呀,什么时候?”

第二次:客户端再发出一个数据包确认服务端的同步要求。“就现在。”

四次挥手

所谓四次挥手就是终止TCP连接的过程,需要客户端和服务端总共发送4个包以确认连接断开。在Socket编程中,这一过程由客户端或服务端任一方执行close来触发。

简而言之:

第一次:客户端向服务端发送断开请求数据包。“今天就聊到这吧”

第二次:服务端向客户端发送同意断开数据包。“嗯,好的”

第三次:服务端再向客户端发送断开请求数据包。“那我挂了呀?”

第四次:客户端再向服务端发送确认断开数据包。“拜拜”

此时服务端断开连接,客户端过会发现服务端没有回复,也断开连接。

实现步骤

alt

UDP与TCP的区别

  1. 基于连接与无连接;
  2. 对系统资源的要求(TCP较多,UDP较少);
  3. UDP程序结构较简单;
  4. 数据报模式与流模式;
  5. TCP保证数据正确性,UDP可能丢包,TCP保证数据顺序,UDP不保证。

alt