C#的线程技术及操作(线程优先级、线程同步)

2023-12-14 06:08:59

目录

一、线程的优先级

二、线程同步

1.使用lock关键字实现线程同步

???????2.使用Monitor类实现线程同步

3.使用Mutex类实现线程同步


一、线程的优先级

????????线程的优先级指定一个线程相对于另一个线程的相对优先级。每个线程都有一个分配的优先级。在公共语言运行库内创建的线程最初被分配为Normal优先级,而在公共语言运行库外创建的线程,在进入公共语言运行库时将保留其先前的优先级。

  • 线程是根据其优先级而调度执行的,用于确定线程执行顺序的调度算法随操作系统的不同而不同。
  • 在某些操作系统下,具有最高优先级(相对于可执行线程而言)的线程经过调度后总是首先运行。
  • 如果具有相同优先级的多个线程都可用,则程序将遍历处于该优先级的线程,并为每个线程提供一个固定的时间片段来执行。
  • 只要具有较高优先级的线程可以运行,具有较低优先级的线程就不会执行。
  • 如果在给定的优先级上不再有可运行的线程,则程序将移到下一个较低的优先级并在该优先级上调度线程以执行
  • 如果具有较高优先级的线程可以运行,则具有较低优先级的线程将被抢先,并允许具有较高优先级的线程再次执行(高优先级的可运行线程会抢占低优先级线程)。
  • 除此之外,当应用程序的用户界面在前台和后台之间移动时,操作系统还可以动态调整线程的优先级。
  • 一个线程的优先级不影响该线程的状态,该线程的状态在操作系统可以调度该线程之前必须为Running

????????线程的优先级值及说明:

优 先 级 值

说 ???明

AboveNormal

可以将Thread安排在具有Highest优先级的线程之后,在具有Normal优先级的线程之前

BelowNormal

可以将Thread安排在具有Normal优先级的线程之后,在具有Lowest优先级的线程之前

Highest

可以将Thread安排在具有任何其他优先级的线程之前

Lowest

可以将Thread安排在具有任何其他优先级的线程之后

Normal

可以将Thread安排在具有AboveNormal优先级的线程之后,在具有BelowNormal优先级的线程之前。默认情况下,线程具有Normal优先级

?

// 创建两个Thread线程类对象,并设置第一个Thread类对象
// 的优先级为最低,然后调用Start()方法开启这两个线程
namespace _05
{
    class Program
    {
        /// <summary>
        /// 使用自定义方法Thread1声明线程
        /// 使用自定义方法Thread2声明线程
        /// 设置线程的调度优先级
        /// 这个例子并不严谨,输出结果不稳定,线程1和2是先后启动,
        /// 操作系统并不能稳定地区分和处置优先级,
        /// 或者即使能区分优先级但是来不及处置。
        /// 一切,都是因为代码编写的太简单了。
        /// </summary>
        static void Main(string[] args)
        {
            Thread thread1 = new(new ThreadStart(Thread1))
            {
                Priority = ThreadPriority.Lowest
            };
            Thread thread2 = new(new ThreadStart(Thread2));
            thread1.Start();                                       //开启线程一
            //Console.WriteLine("测试线程一");
            thread2.Start();                                       //开启线程二
            //Console.WriteLine("测试线程二");
            Console.Read();
        }
        static void Thread1()
        {
            Console.WriteLine("线程一");
        }
        static void Thread2()
        {
            Console.WriteLine("线程二");
        }
    }
}/*
线程二
线程一     */

二、线程同步

????????使用多个线程的一个好处是每个线程都可以异步执行

  • 对于Windows应用程序,耗时的任务可以在后台执行,而使应用程序窗口和控件保持响应。
  • 对于服务器应用程序,多线程处理提供了用不同线程处理每个传入请求的能力;否则,在完全满足前一个请求之前,将无法处理每个新请求。
  • 线程的异步性意味着必须协调对资源(如文件句柄、网络连接和内存)的访问;否则,两个或更多的线程可能在同一时间访问相同的资源,而每个线程都不知道其他线程的操作,结果将产生不可预知的数据损坏。
  • 线程同步是指并发线程高效、有序地访问共享资源所采用的技术。所谓同步,是指某一时刻只有一个线程可以访问资源,只有当资源所有者主动放弃了代码或资源的所有权时,其他线程才可以使用这些资源
  • 线程同步可以分别使用C#中的lock关键字、Monitor类和Mutex实现。

1.使用lock关键字实现线程同步

? ? ? ? lock关键字可以用来确保代码块完成运行,而不会被其他线程中断,它是通过在代码块运行期间为给定对象获取互斥锁来实现的。

????????lock语句以关键字lock开头,它有一个作为参数的对象,在该参数的后面还有一个一次只能由一个线程执行的代码块。lock语句语法格式如下。

Object thisLock =new Object();
lock (thisLock)
{
    //要运行的代码块
}?

????????提供给lock语句的参数必须为基于引用类型的对象,该对象用来定义锁的范围。

????????事实上lock语句是用Monitor类来实现的,它等效于try/finally语句块,使用lock关键字通常比直接使用Monitor类更可取,一方面是因为lock更简洁;另一方面是因为lock确保了即使受保护的代码引发异常,也可以释放基础监视器。这是通过finally关键字来实现的,无论是否引发异常它都执行关联的代码块。

// 自定义一个LockThread()方法,该方法中使用lock关键字
// 锁定当前线程,然后在Main()方法中通过Program的类对象调用LockThread()自定义方法
namespace _06
{
    class Program
    {
        int num = 10;//设置当前总票数
        /// <summary>
        /// Sleep()设置不同值,影响每次启动显示的效果,
        /// 一般数值越小,随机性越大,数值越大,显示结果越稳定
        /// </summary>
        void Ticket()
        {
            while (true)                    //设置无限循环
            {
                lock (this)                 //锁定代码块,以便线程同步
                {
                    if (num > 0)            //判断当前票数是否大于0
                    {
                        Thread.Sleep(300);  //使当前线程休眠100毫秒
                        Console.WriteLine(Thread.CurrentThread.Name + "----票数" + num--);    //票数减1
                    }
                }
            }
        }
        /// <summary>
        /// 创建对象,以便调用对象方法
        /// 分别实例化4个线程,并设置名称
        /// </summary>
        static void Main(string[] args)
        {
            Program p = new();
            Thread tA = new(new ThreadStart(p.Ticket))
            {
                Name = "线程一"
            };
            Thread tB = new(new ThreadStart(p.Ticket))
            {
                Name = "线程二"
            };
            Thread tC = new(new ThreadStart(p.Ticket))
            {
                Name = "线程三"
            };
            Thread tD = new(new ThreadStart(p.Ticket))
            {
                Name = "线程四"
            };
            tA.Start(); //分别启动线程
            tB.Start();
            tC.Start();
            tD.Start();
            Console.ReadLine();
        }
    }
}

???????2.使用Monitor类实现线程同步

????????Monitor类提供了同步对象的访问机制,它通过向单个线程授予对象锁来控制对对象的访问,对象锁提供限制访问代码块(通常称为临界区)的能力。当一个线程拥有对象锁时,其他任何线程都不能获取该锁。

????????Monitor类的主要功能如下。

  • 它根据需要与某个对象相关联。
  • 它是未绑定的,也就是说可以直接从任何上下文调用它。
  • 不能创建Monitor类的实例。
  • 使用Monitor类锁定的是对象(即引用类型)而不是值类型。

????????Monitor类的常用方法及说明

方 ??法

说 ??明

Ente

在指定对象上获取排他锁

Exit

释放指定对象上的排他锁

Puls

通知等待队列中的线程锁定对象状态的更改

PulseAll

通知所有的等待线程对象状态的更改

TryEnter

试图获取指定对象的排他锁

Wait

释放对象上的锁并阻止当前的线程,直到它重新获取该锁

?

// 自定义一个LockThread()方法,该方法中首先使用
// Monitor类的Enter()方法锁定当前的线程。
// 然后再调用Monitor类的Exit()方法释放当前的线程。
// 最后在Main()方法中通过Program的类对象调用LockThread()自定义方法
namespace _07
{
    class Program
    {
        int num = 10;//设置当前总票数
        /// <summary>
        /// 创建对象,以便调用对象方法
        /// 分别实例化4个线程,并设置名称
        /// 分别启动线程
        /// 这个代码仍然不能完全可控,结果仍然随机
        /// </summary>
        static void Main(string[] args)
        {
            Program p = new();
            Thread tA = new(new ThreadStart(p.Ticket))
            {
                Name = "线程一"
            };
            Thread tB = new(new ThreadStart(p.Ticket))
            {
                Name = "线程二"
            };
            Thread tC = new(new ThreadStart(p.Ticket))
            {
                Name = "线程三"
            };
            Thread tD = new(new ThreadStart(p.Ticket))
            {
                Name = "线程四"
            };
            tA.Start(); 
            tB.Start();
            tC.Start();
            tD.Start();
            Console.ReadLine();
        }

        /// <summary>
        /// 使用Monitor实现线程同步
        /// </summary>
        void Ticket()
        {
            while (true)                //设置无限循环
            {
                Monitor.Enter(this);    //锁定当前线程
                if (num > 0)            //判断当前票数是否大于0
                {
                    Thread.Sleep(100);  //使当前线程休眠100毫秒
                    Console.WriteLine(Thread.CurrentThread.Name + "----票数" + num--);//票数减1
                }
                Monitor.Exit(this);      //释放当前线程
            }
        }
    }
}

线程一----票数10
线程二----票数9
线程三----票数8
线程四----票数7
线程一----票数6
线程二----票数5
线程三----票数4
线程四----票数3
线程一----票数2
线程二----票数1

???????3.使用Mutex类实现线程同步

  • 当两个或更多线程需要同时访问一个共享资源时,系统需要使用同步机制来确保一次只有一个线程使用该资源。
  • Mutex类是同步基元,它只向一个线程授予对共享资源的独占访问权。
  • 如果一个线程获取了互斥体,则要获取该互斥体的第二个线程将被挂起,直到第一个线程释放该互斥体。
  • Mutex类与监视器类似,它防止多个线程在某一时间同时执行某个代码块,然而与监视器不同的是,Mutex类可以用来使跨进程的线程同步。
  • 可以使用WaitHandle.WaitOne方法请求互斥体的所属权,拥有互斥体的线程可以在对WaitOne()方法的重复调用中请求相同的互斥体而不会阻止其执行,但线程必须调用同样多次数的ReleaseMutex()方法以释放互斥体的所属权。
  • Mutex类强制线程标识,因此互斥体只能由获得它的线程释放。
  • 当用于进程间同步时,Mutex称为“命名Mutex”,因为它将用于另一个应用程序,因此它不能通过全局变量或静态变量共享。必须给它指定一个名称,才能使两个应用程序访问同一个Mutex对象。

????????Mutex类的常用方法及说明

方法

说 ???明

Close

????在派生类中被重写时,释放由当前WaitHandle持有的所有资源

OpenExisting

????打开现有的已命名互斥体

ReleaseMutex

????释放Mutex一次

SignalAndWait

????原子操作的形式,向一个WaitHandle发出信号并等待另一个

WaitAll

????等待指定数组中的所有元素都收到信号

WaitAny

????等待指定数组中的任一元素收到信号

WaitOn

????当在派生类中重写时,阻止当前线程,直到当前的WaitHandle收到信号

????????使用Mutex类实现线程同步很简单,首先实例化一个Mutex类对象,它的构造函数中比较常用的有public?Mutex(bool initallyOwned)。

????????其中,参数initallyOwned指定了创建该对象的线程是否希望立即获得其所有权,当在一个资源得到保护的类中创建Mutex类对象时,常将该参数设置为false。然后在需要单线程访问的地方调用其等待方法,等待方法请求Mutex对象的所有权。

????????这时,如果该所有权被另一个线程所拥有,则阻塞请求线程,并将其放入等待队列中,请求线程将保持阻塞,直到Mutex对象收到了其所有者线程发出将其释放的信号为止。所有者线程在终止时释放Mutex对象,或者调用ReleaseMutex()方法来释放Mutex对象。

// 自定义了一个LockThread()方法,该方法中首先使用Mutex类对象的WaitOne()方法阻止当前线程。
// 然后再调用Mutex类对象的ReleaseMutex()方法释放Mutex对象,即释放当前的线程。
// 最后在Main()方法中通过Program的类对象调用LockThread()自定义方法
namespace _08
{
    class Program
    {
        int num = 10;//设置当前总票数

        /// <summary>
        /// 创建对象,以便调用对象方法
        /// 分别实例化4个线程,并设置名称
        /// 分别启动线程
        /// </summary>
        static void Main(string[] args)
        {
            Program p = new();
            Thread tA = new(new ThreadStart(p.Ticket))
            {
                Name = "线程一"
            };
            Thread tB = new(new ThreadStart(p.Ticket))
            {
                Name = "线程二"
            };
            Thread tC = new(new ThreadStart(p.Ticket))
            {
                Name = "线程三"
            };
            Thread tD = new(new ThreadStart(p.Ticket))
            {
                Name = "线程四"
            };
            tA.Start(); 
            tB.Start();
            tC.Start();
            tD.Start();
            Console.ReadLine();
        }

        /// <summary>
        /// 使用Mutex实现线程同步
        /// </summary>
        void Ticket()
        {
            while (true)//设置无限循环
            {
                Mutex myMutex = new(false);     //创建Mutex类对象
                myMutex.WaitOne();              //阻塞当前线程
                if (num > 0)                    //判断当前票数是否大于0
                {
                    Thread.Sleep(200);          //使当前线程休眠100毫秒
                    Console.WriteLine(Thread.CurrentThread.Name + "----票数" + num--);//票数减1
                }
                myMutex.ReleaseMutex();         //释放Mutex对象
            }
        }
    }
}

线程一----票数10
线程四----票数9
线程二----票数8
线程三----票数10
线程四----票数6
线程三----票数7
线程二----票数5
线程一----票数4
线程四----票数3
线程二----票数1
线程一----票数2
线程三----票数0

文章来源:https://blog.csdn.net/wenchm/article/details/134911265
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。