面向对象之三大特性,希尔排序

CLR线程池并不会在CLR初始化时立即建立线程,而是在应用程序要创建线程来运行任务时,线程池才初始化一个线程。
线程池初始化时是没有线程的,线程池里的线程的初始化与其他线程一样,但是在完成任务以后,该线程不会自行销毁,而是以挂起的状态返回到线程池。直到应用程序再次向线程池发出请求时,线程池里挂起的线程就会再度激活执行任务。
这样既节省了建立线程所造成的性能损耗,也可以让多个任务反复重用同一线程,从而在应用程序生存期内节约大量开销。

引用:对于大规模乱序数组插入排序很慢,因为它只会交换相邻的元素,因此元素只能一点一点的从数组的一端移动到另一端。例如,如果主键最小的元素正好在数组的尽头,要将它挪到正确的位置就需要N-1次移动。希尔排序为了加快速度简单的改进了插入排序,交换不相邻的元素以对数组的局部进行排序,并最终用插入排序将局部有序的数组排序。

学到封装就会想到访问修饰符,说到访问修饰符,就会想到访问等级,或者说是访问能力的大小。当然也少不了默认的访问类型。

通过CLR线程池所建立的线程总是默认为后台线程,优先级数为ThreadPriority.Normal。

            int[] sort = new int[13] { 1, 4, 89, 34, 56, 40, 59, 60, 39, 1, 40, 90, 48 };  // 输入一个数组
            int h = 1;
            int length = sort.Length;
            while (h > length / 3)
            {
                h = 3 * h + 1;      // 1,4,13,40,121,364,1093,...
            }   // h的初始值根据数组元素多少而定
            while (h >= 1)  // 当h=1 时排序完成
            {
                for (int i = h; i < length; i++)  // 将间隔为h的元素排序
                {
                    for (int j = i; j >= h && sort[j] < sort[j - h]; j -= h) // 当满足这两个条件时交换 数值
                    {
                        int temp = sort[j];
                        sort[j] = sort[j - h];
                        sort[j - h] = temp;
                    }
                }
                h = h / 3;
            }
            for (int i = 0; i < sort.Length; i++)  // 输出
               {
                    Console.Write(sort[i] + " ");
               }
  1. C# 方法默认访问级别 : private (私有的) 
  2. C# 类默认访问级别 : internal    (内部的) 

CLR线程池分为工作者线程(workerThreads)I/O线程(completionPortThreads)两种:

  备注:文字和代码有参考到书籍:算法 第四版(人民邮电出版社) 希尔排序 (162-163)

封装 被定义为"把一个或多个项目封闭在一个物理的或者逻辑的包中"。在面向对象程序设计方法论中,封装是为了防止对实现细节的访问。

  • 工作者线程是主要用作管理CLR内部对象的运作,通常用于计算密集的任务。
  • I/O(Input/Output)线程主要用于与外部系统交互信息,如输入输出,CPU仅需在任务开始的时候,将任务的参数传递给设备,然后启动硬件设备即可。等任务完成的时候,CPU收到一个通知,一般来说是一个硬件的中断信号,此时CPU继续后继的处理工作。在处理过程中,CPU是不必完全参与处理过程的,如果正在运行的线程不交出CPU的控制权,那么线程也只能处于等待状态,即使操作系统将当前的CPU调度给其他线程,此时线程所占用的空间还是被占用,而并没有CPU处理这个线程,可能出现线程资源浪费的问题。如果这是一个网络服务程序,每一个网络连接都使用一个线程管理,可能出现大量线程都在等待网络通信,随着网络连接的不断增加,处于等待状态的线程将会很消耗尽所有的内存资源。可以考虑使用线程池解决这个问题。

抽象和封装是面向对象程序设计的相关特性。抽象允许相关信息可视化,封装则使开发者实现所需级别的抽象

  线程池的最大值一般默认为1000、2000。当大于此数目的请求时,将保持排队状态,直到线程池里有线程可用。

C# 封装根据具体的需要,设置使用者的访问权限,并通过 访问修饰符 来实现。

  使用CLR线程池的工作者线程一般有两种方式:

一个 访问修饰符 定义了一个类成员的范围和可见性。C# 支持的访问修饰符如下所示:

  • 通过ThreadPool.QueueUserWorkItem()方法;
  • 通过委托;
  • public:所有对象都可以访问;(公共的)
  • private:对象本身在对象内部可以访问;(私有的)
  • protected:只有该类对象及其子类对象可以访问;(受保护的)
  • internal:同一个程序集的对象可以访问;(内部的)
  • protected internal:访问限于当前程序集或派生自包含类的类型。(内部受保护的)

  要注意,不论是通过ThreadPool.QueueUserWorkItem()还是委托,调用的都是线程池里的线程。

对于访问修饰符中 涉及的程序集和命名空间 :   

通过以下两个方法可以读取和设置CLR线程池中工作者线程与I/O线程的最大线程数。

程式集:IL+元数据

  1. ThreadPool.GetMax(out in workerThreads,out int completionPortThreads);
  2. ThreadPool.SetMax(int workerThreads,int completionPortThreads);

如果说命名空间是类库的逻辑组织形式,那么程序集就是类库的物理组织形式。只有同时指定类型所在的命名空间及实现该类型的程序集,才能完全限定该类型。(摘抄自《精通.NET核心技术--原来与架构》 电子工业出版社)

  若想测试线程池中有多少线程正在投入使用,可以通过ThreadPool.GetAvailableThreads(out in workThreads,out int conoletionPortThreads)方法。

例如我们要用A类,则需要把包含A类的程序集(即*.DLL)引用到该工程中(物理);而在程序中要声明A类的命名空间(逻辑)。

方法 说明
GetAvailableThreads 剩余空闲线程数
GetMaxThreads 最多可用线程数,所有大于此数目的请求将保持排队状态,直到线程池线程变为可用
GetMinThreads 检索线程池在新请求预测中维护的空闲线程数
QueueUserWorkItem 启动线程池里得一个线程(队列的方式,如线程池暂时没空闲线程,则进入队列排队)
SetMaxThreads 设置线程池中的最大线程数
SetMinThreads 设置线程池最少需要保留的线程数

 

我们可以使用线程池来解决上面的大部分问题,跟使用单个线程相比,使用线程池有如下优点:

Public(公共的) 访问修饰符

Public 访问修饰符允许一个类将其成员变量和成员函数暴露给其他的函数和对象。任何公有成员可以被外部的类访问

下面是个小例子

using System;

namespace ConsoleApp1
{
    class Rectangle
    {
        public double length { get; set; }
        public double width { get; set; }

        public double Getarea()
        {
            return length * width;
        }

        public void Display()
        {
            Console.WriteLine($"长度:{length}");
            Console.WriteLine($"宽度:{width}");
            Console.WriteLine($"面积:{Getarea()}");
        }
    }




    class Program
    {
        static void Main(string[] args)
        {
            Rectangle rectangle = new Rectangle();
            rectangle.width = 55.5;
            rectangle.length = 12.32;
            rectangle.Getarea();
            rectangle.Display();
            Console.ReadKey(true);
        }
    }
}

 

当上面的代码被编译和执行时,它会产生下列结果:

长度:12.32
宽度: 55.5 
面积: 683.76

在上面的实例中,成员变量 length 和 width 被声明为 public,所以它们可以被函数 Main() 使用 Rectangle 类的实例  rectangle 访问。

成员函数 Display() 和 GetArea() 可以直接访问这些变量。

成员函数 Display() 也被声明为 public,所以它也能被 Main() 使用 Rectangle 类的实例 rectangle 访问。

1、缩短应用程序的响应时间。因为在线程池中有线程的线程处于等待分配任务状态(只要没有超过线程池的最大上限),无需创建线程。

Private(私有的) 访问修饰符

Private 访问修饰符允许一个类将其成员变量和成员函数对其他的函数和对象进行隐藏。只有同一个类中的函数可以访问它的私有成员。即使是类的实例也不能访问它的私有成员。

using System;

namespace ConsoleApp1
{
    class Rectangle
    {
        private double length { get; set; }
        private double width { get; set; }

        public void Acceptdetails()
        {
            Console.WriteLine("请输入长度:");
            length = Convert.ToDouble(Console.ReadLine());
            Console.WriteLine("请输入宽度:");
            width = Convert.ToDouble(Console.ReadLine());
        }

        public double Getarea()
        {
            return length * width;
        }

        public void Display()
        {
            Console.WriteLine($"长度:{length}");
            Console.WriteLine($"宽度:{width}");
            Console.WriteLine($"面积:{Getarea()}");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Rectangle rectangle = new Rectangle();
            rectangle.Getarea();
            rectangle.Display();
            Console.ReadKey(true);
        }
    }
}

在上面的实例中,成员变量 length 和 width 被声明为 private,所以它们不能被函数 Main() 访问。

成员函数 AcceptDetails() 和 Display() 可以访问这些变量。

由于成员函数 AcceptDetails() 和 Display() 被声明为 public,所以它们可以被 Main() 使用 Rectangle 类的实例 rectangle 访问。

这一段讲的是private 而不是public 所以请认真看这两个访问修饰符,我第一次看的时候也看错了。所以请认真对待!

2、不必管理和维护生存周期短暂的线程,不用在创建时为其分配资源,在其执行完任务之后释放资源。

Protected 访问修饰符

Protected 访问修饰符允许子类访问它的基类的成员变量和成员函数。这样有助于实现继承。 放到继承的章节详细讨论这个。 

3、线程池会根据当前系统特点对池内的线程进行优化处理。

Internal 访问修饰符

Internal 访问说明符允许一个类将其成员变量和成员函数暴露给当前程序中的其他函数和对象。

换句话说,带有 internal 访问修饰符的 任何成员 可以被定义在该成员所定义的应用程序内的任何类或方法访问。 

using System;

namespace RectangleDemo 
{
    class Rectangle
    {
        //成员变量
        internal double length;
        internal double width;

        double GetArea()
        {
            return length * width;
        }
       public void Display()
        {
            Console.WriteLine("长度: {0}", length);
            Console.WriteLine("宽度: {0}", width);
            Console.WriteLine("面积: {0}", GetArea());
        }
    }   
    class ExecuteRectangle
    {
        static void Main(string[] args)
        {
            Rectangle r = new Rectangle();
            r.length = 4.5;
            r.width = 3.5;
            r.Display();
            Console.ReadLine();
        }
    }
}

当上面的代码被编译和执行时,它会产生下列结果:

长度: 4.5
宽度: 3.5
面积: 15.75

在上面的实例中,请注意成员函数 GetArea() 声明的时候不带有任何访问修饰符。如果没有指定访问修饰符,则使用类成员的默认访问修饰符,即为 private

总之使用线程池的作用就是减少创建和销毁线程的系统开销。在.NET中有一个线程的类ThreadPool,它提供了线程池的管理。

Protected Internal 访问修饰符

Protected Internal 访问修饰符允许在本类,派生类或者包含该类的程序集中访问。这也被用于实现继承。   

ThreadPool是一个静态类,它没有构造函数,对外提供的函数也全部是静态的。其中有一个QueueUserWorkItem方法,它有两种重载形式,如下:

public static bool QueueUserWorkItem(WaitCallback callBack):将方法排入队列以便执行。此方法在有线程池线程变得可用时执行。

public static bool QueueUserWorkItem(WaitCallback callBack,Object state):将方法排入队列以便执行,并指定包含该方法所用数据的对象。此方法在有线程池线程变得可用时执行。

QueueUserWorkItem方法中使用的的WaitCallback参数表示一个delegate,它的声明如下:

本文由澳门新葡亰平台官网发布于编程,转载请注明出处:面向对象之三大特性,希尔排序

TAG标签:
Ctrl+D 将本页面保存为书签,全面了解最新资讯,方便快捷。