进程及进程间通信

基础知识

1.进程:具有独立功能的程序在一个数据集合上一次动态的执行过程。通俗点讲就是“一个正在运行的程序”

2.程序:静态的程序以文件的形式保存在磁盘上。

3.操作系统的进程管理:

每一个正在运行的程序都对应着一个独立的进程,当这些程序装入内存开始执行时,操作系统会为每个进程创建好相关的数据结构。由于操作系统可以同时装入多个程序,为此必须有一种方法来保证这些同时运行的程序不相互影响,不会由于一个程序出现异常而直接影响其他程序,甚至操作系统的正常运行。位于操作系统核心的“进程管理“模块负责管理并行执行的多个程序。

4.操心系统的用户模式和核心模式:

Windows设计了两种代码运行的环境——用户模式和核心模式(用户态与核心态)。普通的应用程序运行于用户模式中,而操作系统的关键代码(比如负责分配与回收内存,创建和销毁进程等功能代码)运行核心模式下。运行于核心模式下的代码可以访问系统内存和执行所有的CPU指令,用户模式下运行的代码则只拥有”有限的权限“。当用户模式下的应用程序访问系统核心数据时,它必须发出一个”系统调用“提出访问核心数据的申请,操作系统内核接到申请,有运行在核心模式下的代码去访问这些数据,然后将结果再”转发“给处于用户模式下的代码。Windows应用程序通过调用Win32API函数来实现从”用户模式“到”核心模式“的转换。

5.句柄:

操作系统核心会不断地创建和销毁”核心对象”,为了便于跟踪和访问这些对象,此操作系统为这些对象分配了标识,这是一个32位的整数,被称为”句柄(Handle)”

6.操作系统采用引用计数法来决定销毁核心对象的时机:

当一个线程调用某个Win32API函数创建一个核心对象之后,此核心对象的初始引用计数为1,应用程序代码中对其句柄的每次引用都会导致计数加1.线程用完以后应该关闭句柄,此时引用对象减1,当其值减为1时,操作系统销毁这一核心对象,并回收其占用的各种资源。

7..NET对于”普通对象”与”核心对象”不加区分,使用new关键字就可以创建任何一种类型,而对象的销毁工作由CLR负责。

8.线程

线程是CPU调度的基本单位,因为操作系统会给进程分配大量的资源,如果直接以进程作为调度CPU运行的基本单位,那么当进程投入运行和退出运行时,必然花费大量的计算资源在进程运行环境的切换上(保护现场等)。这会严重影响操作系统的性能。为此,操作系统将进程切分为多个“线程(thread)”,虽然线程也需要有一个运行环境,但这个运行环境往往只涉及到当前一些寄存器的值,数据量小,切换速度快得多。

9. 进程是系统分配各种资源的单位,而线程则是操作系统分配CPU的基本单位

10.线程上下文

线程的运行环境被称为“线程上下文”,包括为使线程在线程的所属进程地址空间中继续运行所需的所有信息(如线程的CPU寄存器,线程堆栈和线程局部存储区)当从一个线程切换到另一个线程时,操作系统将保存被换出线程的线程上下文。

11.CLR如何管理进程与线程

.NET是一个托管的运行环境,在其上运行的进程称为“托管进程”,而在托管进程中创建的线程自然就是“托管线程”。操作系统直接创建的进程和线程被称为本地进程和本地线程

Process类代表托管进程,它实际上封装的是本地进程。,每一个托管进程都对应着一个操作系统真实的本地进程。

Thread类代表托管线程,每一个托管线程对应着一个函数(线程函数),托管线程执行的过程就是线程函数执行的过程,线程函数的代码执行完了,线程也就执行完了。

Thread类与操心系统的真实的本地线程不是一一对应的,它代表是一个“逻辑线程”,有CLR负责创建与管理。.NET Framework中另有一个ProcessThread用于表示操作系统真实的本地线程。

在操作系统中,线程直接运行于进程内部,而在.NET托管环境下,托管线程与托管进程之间还有一个中间层次——应用程序域

2.进程通信

进程通信:正在运行的进程相互交换信息。

每个进程都拥有自己的地址空间,其它进程不能直接访问。通常通过一个第三方媒介间接地在进程之间交换信息。通常有下面几种方式:

剪贴板,共享同一文件,COM,.NET4.0内存映射文件,WCF

①使用剪贴板在进程间传送对象

剪贴板是一个供应用程序使用的公共区域,在Windows上运行的所有程序在需要时都可以使用剪贴板存放的信息

namespace UseClipboard
{
    public partial class frmMain : Form
    {
        public frmMain()
        {
            InitializeComponent();
        }
        //图片
        private Image bmp
        {
            get
            {
                return pictureBox1.Image;
            }
            set
            {
                pictureBox1.Image = value;
            }
        }
        //图片说明
        private string info
        {
            get
            {
                return txtImageInfo.Text;
            }
            set
            {
                txtImageInfo.Text = value;
            }
        }

       

        private void btnLoadPic_Click(object sender, EventArgs e)
        {
            ChooseImageFile();
        }

        //选择图片
        private void ChooseImageFile()
        {
            if (openFileDialog1.ShowDialog() == DialogResult.OK)
            {
                bmp = new Bitmap(openFileDialog1.FileName);
            }
        }
        //根据用户设定的信息创建对象
        private MyPic CreateMyPicObj()
        {
            MyPic obj = new MyPic();
            obj.pic = bmp;
            obj.picInfo = info;
            return obj;
        }

        //将对象复制到剪贴板上
        private void CopyToClipboard()
        {
            //创建MyPic对象
            MyPic obj = CreateMyPicObj();
         
            //创建一个数据对象,将MyPic类型的对象装入
            IDataObject dataobj = new DataObject(obj);
            //其它类型的数据也可以装入到数据对象中
            dataobj.SetData(DataFormats.UnicodeText, info);
            dataobj.SetData(DataFormats.Bitmap, bmp);
            //复制到剪贴板上,第二个参数表明程序退出时不清空剪贴板
            Clipboard.SetDataObject(dataobj,true );
        }

        private void btnExit_Click(object sender, EventArgs e)
        {
            Close();
           
        }

        private void btnCopyToClipboard_Click(object sender, EventArgs e)
        {
            CopyToClipboard();
        }

        //从剪贴板获取数据
        private void PasteFromClipboard()
        {
            //剪贴板上有我需要的数据吗?格式为“项目名称.数据格式名”
            if (Clipboard.ContainsData("UseClipboard.MyPic") == false)
                return;
            //读取数据
            IDataObject clipobj = Clipboard.GetDataObject();
            //将数据转换为需要的类型
            MyPic mypicobj = clipobj.GetData("UseClipboard.MyPic") as MyPic;
            //从数据对象中分解出需要的数据
            info = mypicobj.picInfo;
            pictureBox1.Image = mypicobj.pic;
        }

        private void btnPasteFromClipboard_Click(object sender, EventArgs e)
        {
            PasteFromClipboard();
        }

    
    }
}

剪贴板无法通知其它进程数据已经放到剪贴板上了。

②使用FileSystemWatcher实现进程同步

FileSystemWatcher是.NET提供组件,它可以监控特定的文件夹或文件,比如在此文件夹中的文件被删除或内容被改变时引发对应的事件。通过使用FileSystemWatcher组件,让多个进程同时监控一个文件,就可以让文件充当“临时的”进程间通信渠道。

③使用内存映射文件实现进程通信

原理:在内存中开辟出一块存放数据的专用区域,这区域往往与硬盘上特定的文件相对应。进程将这块内存区域映射到自己的地址空间中,访问它就像访问普通内存一样。

MemoryMappedFile对象表示一个内存映射文件,通过它的CreateFromFile根据磁盘现有文件创建内存映射文件。

MemoryMappedFile对象创建之后,并不能直接对其进行读写,必须通过MemoryMappedViewAccessor来访问创建的内存映射文件,MemoryMappedViewAccessor的Write,Read方法来执行读写操作。注意:T必须是值类型。之所以不能是引用类型,是因为引用类型保存在托管堆上面,计算机需要消耗性能来计算对象的大小。

如果需要保存引用类型(例如图片),可以采用序列化的方式。MemoryMappedFile类可以创建一个MemoryMappedViewStream对象,通过它可以序列化对象。如下:

//创建或打开内存映射文件
MemoryMappedFile memoryFile=MemoryMappedFile.CreateOrOpen(...);
//创建内存映射流
MemoryMappedViewStream stream=memoryFile.CreateViewStream();
//创建要在进程之间交换的信息对象
MyObj=...
//向内存映射流中序列化对象
IFormatter formatter=new BinaryFormatter();
stream.Seek(0,SeekOrigin.Begin);
formatter.Serialize(stream,obj);

④使用WCF通过管道实现进程通信

管道(pipe):是Windows提供的一种进程间通信机制,用于在两个进程间相互传送数据。

Windows提供两种管道:

匿名管道(Anonymous Pipe):单向通信,两个通信的进程应该是父子关系,父进程在创建子进程时,负责将代表匿名管道的句柄传给子进程,子进程获得句柄后,即可接收从父进程发来的消息。

命名管道(Named Pipe):拥有在本机唯一的名字,可以用在一个服务端进程和多个客户进程同事进行单向或双向通信。命名管道支持基于消息的通信模式,这就是说,一个进程可以向另一方进程连续发送多个消息

读书笔记《.NET4.0面向对象编程漫谈》作者:金旭亮老师

作者:张雪飞
出处:https://zhangxuefei.site/p/61
版权说明:欢迎转载,但必须注明出处,并在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

发表评论

电子邮件地址不会被公开。 必填项已用*标注