qthread中获取当前优先级_【自学C#】|| 笔记 38 Priority:多线程优先级设置

222a1ca9a2ea05e72c0b7af241d6fdb3.png

一、Priority:多线程优先级设置

    在《C# ThreadStart》一节中我们通过两个线程分别打印奇数和偶数,但是每次打印出来的结果是不同的。
    如果需要控制输出值的顺序,可以通过对线程优先级的设置以及线程调度来实现。
    在 C# 中线程的优先级使用线程的 Priority 属性设置即可,默认的优先级是 Normal。
    在设置优先级后,优先级高的线程将优先执行。
    优先级的值通过 ThreadPriority 枚举类型来设置,从低到高分别为Lowest、BelowNormal、Normal、AboveNormal、Highest。

    1.例:

    通过设置线程的优先级来控制输出奇数和偶数的线程,为了看出设置线程优先级的效果将输出 1〜100 中的奇数和 0〜100 中的偶数。

using System;using System.Diagnostics;using System.Threading;namespace ConsoleApp1{    class Program    {        static void Main(string[] args)        {            ThreadStart ts1 = new ThreadStart(PrintEven);            Thread t1 = new Thread(ts1);            //设置打印偶数线程的优先级            t1.Priority = ThreadPriority.Lowest;                        ThreadStart ts2 = new ThreadStart(PrintOdd);            Thread t2 = new Thread(ts2);            //设置打印奇数线程的优先级            t2.Priority = ThreadPriority.Highest;                        t1.Start();            t2.Start();        }        //打印1~100中的奇数        public static void PrintOdd()        {            for (int i = 1; i <= 100; i = i + 2)            {                Console.WriteLine(i);            }        }        //打印0~100中的偶数        public static void PrintEven()        {            for (int i = 0; i <= 100; i = i + 2)            {                Console.WriteLine(i);            }        }    }    }

分析:

    首先是创建两个静态类,分别是输出奇数和偶数的功能效果。

    然后是Main()主函数里的内容。

        创新是创建进程委托,然后放进Thread 类中。

            .Priority属性,是获取或设置线程的优先级。

            ThreadPriority是一个静态类,线程在调度时的优先级枚举值,包括 Highest、AboveNormal、Normal、BelowNormal、Lowest。

    最近进行启动进程。

运行结果:

3962b6745fa387390889d768cdcd2905.png

    总之是输出了100以内的奇数和偶数。

    从上面的运行效果可以看出,由于输岀奇数的线程的优先级高于输出偶数的线程,所以在输出结果中优先输出奇数的次数会更多。
    此外,每次输出的结果也不是固定的。通过优先级是不能控制线程中的先后执行顺序的,只能是优先级高的线程优先执行的次数多而已。
    线程状态控制的方法包括暂停线程 (Sleep)、中断线程 (Interrupt)、挂起线程 (Suspend)、唤醒线程 (Resume)、终止线程 (Abort)。

    下面通过实例来演示线程控制的效果。

    2.例

    使用暂停线程 (Sleep) 的方法让打印奇数和打印偶数的线程交替执行,即打印 0〜10 的数。

using System;using System.Diagnostics;using System.Threading;namespace ConsoleApp1{    class Program    {        static void Main(string[] args)        {            ThreadStart ts1 = new ThreadStart(PrintOdd);            Thread t1 = new Thread(ts1);            ThreadStart ts2 = new ThreadStart(PrintEven);            Thread t2 = new Thread(ts2);            t1.Start();            t2.Start();        }        //打印1~100中的奇数        public static void PrintOdd()        {            for (int i = 1; i <= 10; i = i + 2)            {                //让线程休眠 1 秒                Thread.Sleep(1000);                Console.WriteLine(i);            }        }        //打印0~100中的偶数        public static void PrintEven()        {            for (int i = 0; i <= 10; i = i + 2)            {                Console.WriteLine(i);                //让线程休眠 1 秒                Thread.Sleep(1000);            }        }    }    }

分析:

    首先是定义两个静态的方法,里面的Thread.Sleep(1000);是将当前线程暂停指定的毫秒数。这里是1秒=1000毫秒。

    创建、定义、启动线程。

004dc9a3d5859305e9b40ad1d6fd3696.png

    会每个1秒输出一个。

    从上面的运行效果可以看出,通过 Sleep 方法能控制两个线程执行的先后顺序。
    需要注意的是,两个线程虽然交替执行,但每次运行该程序的效果依然是不同的。    

    3.例

    模拟发放 10 个红包,当剩余 5 个红包时线程终止。

using System;using System.Diagnostics;using System.Threading;namespace ConsoleApp1{    class Program    {        private static int count = 10;        private static void GiveRedEnvelop()        {            while (count > 0)            {                count--;                if (count == 4)                {                    //终止当前线程                    Console.WriteLine("红包暂停发放!");                    Thread.CurrentThread.Abort();                }                Console.WriteLine("剩余 {0} 个红包", count);            }        }        static void Main(string[] args)        {            ThreadStart ts = new ThreadStart(GiveRedEnvelop);            Thread t = new Thread(ts);            t.Start();        }            }    }

分析:

    首先定义了一个计数器conut。

    然后是一个方法,并且当剩余第5个红包时,中断当前进程。

    再然后就是进程的固定写法了。

        先创建进程委托,再存进Thread类中,再通过Start()方法进行启动进程。

运行结果:

    运行是会报错,通过使用t.IsBackground属性进行判断,就可以避免报错。

    请参考下面的案例4。

08a69a765269cd6492e62d7225545a37.png

    而即便报错并关闭调试,但输出结果却并没有跟着一起消失。

23b92fd72a017c8cb69567d8357c89b8.png

    目前,由于挂起线程 (Suspend) 和唤醒线程 (Resume) 的操作很容易造成线程的死锁状态,已经被弃用了,而是使用标识字段来设置线程挂起和唤醒的状态。
    所谓线程死锁就是多个线程之间处于相互等待的状态。
    线程分为前台线程和后台线程,前台线程不用等主程序结束,后台线程则需要应用程序运行结束后才能结束。
    此外,在应用程序运行结束后,后台线程即使没有运行完也会结束,前台线程必须等待自身线程运行结束后才会结束。
    使用 Thread 对象的 IsBackground 属性来判断线程是否为后台线程。

    4.例

    在上一实例的基础上判断发红包的线程是否为后台线程,如果不是后台线程,将其设置为后台线程。

    根据题目要求,这里只在 Main() 方法中添加了对线程是否为后台线程的判断,Main() 方法中的代码如下。

using System;using System.Diagnostics;using System.Threading;namespace ConsoleApp1{    class Program    {        private static int count = 10;        private static void GiveRedEnvelop()        {            while (count > 0)            {                count--;                if (count == 4)                {                    //终止当前线程                    Console.WriteLine("红包暂停发放!");                    Thread.CurrentThread.Abort();                }                Console.WriteLine("剩余 {0} 个红包", count);            }        }        static void Main(string[] args)        {            ThreadStart ts = new ThreadStart(GiveRedEnvelop);            Thread t = new Thread(ts);            t.Start();            if (t.IsBackground == false)            {                Console.WriteLine("该线程不是后台线程!");                t.IsBackground = true;            }            else            {                Console.WriteLine("该线程是后台线程!");            }        }            }    }

分析:

    首先依旧是定义用来计数的变量和停止进程的方法。

    然后在Main()方法中,通过使用t.IsBackground属性进行判断,就可以避免出现报错了。

运行结果:

8e42c413279e962d4d6e93c43b762556.png

运行该程序,直接输出“该线程不是后台线程!”,由于将该线程设置为后台线程,则不会输出红包发放的信息。

二、lock:给线程加锁,保证线程同步

    虽然 Sleep 方法能控制线程的暂停时间,从而改变多个线程之间的先后顺序,但每次调用线程的结果是随机的。
    线程同步的方法是将线程资源共享,允许控制每次执行一个线程,并交替执行每个线程。
    在 C# 语言中实现线程同步可以使用 lock 关键字和 Monitor 类、Mutex 类来解决。
    对于线程同步操作最简单的一种方式就是使用 lock 关键字,通过 lock 关键字能保证加锁的线程只有在执行完成后才能执行其他线程。
    关于 Monitor 类和 Mutex 类的应用,下面会有介绍。

    lock 的语法形式如下。

        lock(object)
        {
            //临界区代码
        }

    这里 lock 后面通常是一个 Object 类型的值,也可以使用 this 关键字来表示。
    最好是在 lock 中使用私有的非静态或负变量或私有的静态成员变量,即使用 Private 或 Private static 修饰的成员。

    例:

private Object obj = new Object();lock (obj){    //临界区代码}

    1.例

    创建控制台应用程序,使用 lock 关键字控制打印奇数和偶数的线程,要求先执行奇数线程,再执行偶数线程。

using System;using System.Diagnostics;using System.Threading;namespace ConsoleApp1{    class Program    {               static void Main(string[] args)        {            Program program = new Program();                        ThreadStart ts1 = new ThreadStart(program.PrintOdd);            Thread t1 = new Thread(ts1);            t1.Name = "打印奇数的线程";            t1.Start();                        ThreadStart ts2 = new ThreadStart(program.PrintEven);            Thread t2 = new Thread(ts2);            t2.Name = "打印偶数的线程";            t2.Start();        }        public void PrintEven()        {            lock (this)            {                for (int i = 0; i <= 10; i = i + 2)                {                    Console.WriteLine(Thread.CurrentThread.Name + "--" + i);                }            }        }        public void PrintOdd()        {            lock (this)            {                for (int i = 1; i <= 10; i = i + 2)                {                    Console.WriteLine(Thread.CurrentThread.Name + "--" + i);                }            }        }    }    }

分析:

    首先是创建两个方法,并放进lock()中,实现线程同步。

        而Thread.CurrentThread.Name,目的是输出之前存入的字符串。

    然后是Main()中的程序。

        首先之所以会定义new Program()实例,是因为这里没有使用静态的方式创建方法。

        然后就是固定的创建线程委托ts1,并存进Thread类t1中。

        并为其Name属性赋值,然后启动线程。

    再然后继续创建线程委托ts2,并存进Thread类t2中。

        并为其Name属性赋值,然后启动线程。

7364ba4f2c2885f00f73f8dd5a68bae2.png

    从上面的运行效果可以看出,当打印奇数的线程结束后才执行打印偶数的线程,并且每次打印的效果是一样的。

    总之就是加了lock的方法,在进行线程委托时可实现给线程加锁,保证线程同步。

三、Monitor:锁定资源

    在 C# 中 Monitor 类的命名空间是 System.Threading,它的用法要比《C# lock》一节中介绍的 lock 的用法复杂一些,但本质是一样的。
    使用 Monitor 类锁定资源的代码如下。

        Monitor.Enter(object);
        try
        {
            //临界区代码
        }
        finally
        {
            Monitor.Exit(object);
        }

    在这里,object 值与 lock 中的 object 值是一样的。
    简而言之,lock 的写法是 Monitor 类的一种简写。

    1.例

    将上一面的实例中的 lock 关键字替换成 Monitor 类。

using System;using System.Diagnostics;using System.Threading;namespace ConsoleApp1{    class Program    {                static void Main(string[] args)        {            Program program = new Program();            ThreadStart ts1 = new ThreadStart(program.PrintOdd);            Thread t1 = new Thread(ts1);            t1.Name = "打印奇数的线程";            t1.Start();            ThreadStart ts2 = new ThreadStart(program.PrintEven);            Thread t2 = new Thread(ts2);            t2.Name = "打印偶数的线程";            t2.Start();        }        public void PrintEven()        {            Monitor.Enter(this);            try            {                for (int i = 0; i <= 10; i = i + 2)                {                    Console.WriteLine(Thread.CurrentThread.Name + "--" + i);                }            }            finally            {                Monitor.Exit(this);            }        }        public void PrintOdd()        {            Monitor.Enter(this);            try            {                for (int i = 1; i <= 10; i = i + 2)                {                    Console.WriteLine(Thread.CurrentThread.Name + "--" + i);                }            }            finally            {                Monitor.Exit(this);            }        }    }    }

分析:

    首先定义两个类。

        使用Monitor.Enter(this);方式进行定义,更lock()进行替换。

        然后通过try语句,进行判断输出。

    而在Main()方法中,一样先定义当前类的实例,往进程委托中存入方法,并放进Thread类中,再给其Name赋值,并启动进程。

运行结果:

f411d1f33ba62d10e29955d56d3243e6.png

    Monitor 类的用法虽然比 lock 关键字复杂,但其能添加等待获得锁定的超时值,这样就不会无限期等待获得对象锁。
    使用 TryEnter() 方法可以给它传送一个超时值,决定等待获得对象锁的最长时间。
    使用 TryEnter() 方法设置获得对象锁的时间的代码如下。

        Monitor.TryEnter(object, 毫秒数 );

    该方法能在指定的毫秒数内结束线程,这样能避免线程之间的死锁现象。
    此外,还能使用 Monitor 类中的 Wait() 方法让线程等待一定的时间,使用 Pulse() 方法通知处于等待状态的线程。

四、Mutex:(互斥锁)线程同步

    C# 中 Mutex 类也是用于线程同步操作的类,例如,当多个线程同时访问一个资源时保证一次只能有一个线程访问资源。
    在 Mutex 类中,WaitOne() 方法用于等待资源被释放, ReleaseMutex() 方法用于释放资源。
    WaitOne() 方法在等待 ReleaseMutex() 方法执行后才会结束。


    1.例

    使用线程互斥实现每个车位每次只能停一辆车的功能。

    根据题目要求,停车位即为共享资源,实现的代码如下。

using System;using System.Diagnostics;using System.Threading;namespace ConsoleApp1{    class Program    {        static void Main(string[] args)        {            ParameterizedThreadStart ts = new ParameterizedThreadStart(PakingSpace);            Thread t1 = new Thread(ts);            t1.Start("冀A12345");                        Thread t2 = new Thread(ts);            t2.Start("京A00000");        }        private static Mutex mutex = new Mutex();        public static void PakingSpace(object num)        {            if (mutex.WaitOne())            {                try                {                    Console.WriteLine("车牌号{0}的车驶入!", num);                    Thread.Sleep(1000);                }                finally                {                    Console.WriteLine("车牌号{0}的车离开!", num);                    mutex.ReleaseMutex();                }            }        }    }}

分析:

    首先是19~35行

        创建Mutex 静态类实例。

        然后又自定义了一个PakingSpace类,目的是记录车牌号的驶入和驶出。

            Sleep()等待时间。

            .ReleaseMutex()用于释放资源。

                  WaitOne() 方法在等待 ReleaseMutex() 方法执行后才会结束。

        在Main()方法中进行进程的启动。

            先创建进程的委托,这里使用的是带参数的那个。

            然后存入Thread t1 = new Thread(ts);中。

            在启动进程时,进行传参。

运行结果:

f3dbeb21e6a1678d90b81a461968a424.png

    从上面的运行效果可以看出,每辆车驶入并离开后其他车才能占用停车位,即当一个线程占用资源时,其他线程是不使用该资源的。     

五、总结

    由于进程和线程还是没弄明白,所以又找了一些科普的概念进行一些补全。   

    进程

    一个在内存中运行的应用程序。

    每个进程都有自己独立的一块内存空间,一个进程可以有多个线程,比如在Windows系统中,一个运行的xx.exe就是一个进程。

    线程

    进程中的一个执行任务(控制单元),负责当前进程中程序的执行。

    一个进程至少有一个线程,一个进程可以运行多个线程,多个线程可共享数据。

    与进程不同的是同类的多个线程共享进程的堆和方法区资源,但每个线程有自己的程序计数器、虚拟机栈和本地方法栈,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。

    进程与线程的区别总结

    线程具有许多传统进程所具有的特征,故又称为轻型进程(Light—Weight Process)或进程元;

    而把传统的进程称为重型进程(Heavy—Weight Process),它相当于只有一个线程的任务。

    在引入了线程的操作系统中,通常一个进程都有若干个线程,至少包含一个线程。

    根本区别:

        进程是操作系统资源分配的基本单位,而线程是处理器任务调度和执行的基本单位

    资源开销:

        每个进程都有独立的代码和数据空间(程序上下文),程序之间的切换会有较大的开销;

        线程可以看做轻量级的进程,同一类线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计数器(PC),线程之间切换的开销小。

    包含关系:

        如果一个进程内有多个线程,则执行过程不是一条线的,而是多条线(线程)共同完成的;线程是进程的一部分,所以线程也被称为轻权进程或者轻量级进程。

    内存分配:

        同一进程的线程共享本进程的地址空间和资源,而进程之间的地址空间和资源是相互独立的

    影响关系:

    一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃整个进程都死掉。所以多进程要比多线程健壮。

    执行过程:

    每个独立的进程有程序运行的入口、顺序执行序列和程序出口。但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制,两者均可并发执行。

    使用 ParameterizedThreadStart 创建进程。

    首先需要创建 ParameterizedThreadStart 委托的实例,然后再创建 Thread 类的实例。

具体的代码如下。

ParameterizedThreadStart pts=new ParameterizedThreadStart( 方法名 );
Thread t=new Thread(pts);

    使用 ThreadStart 创建线程。

    首先需要创建 ThreadStart 委托的实例,然后再创建 Thread 类的实例。具体的代码如下。

ThreadStart ts = new ThreadStart( 方法名 );
Thread t = new Thread(ts);

    总之进程大于线程,而进程也好,线程也罢,都是在CPU中的一块空间。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值