初探C#线程

C#多线程的使用

想要创建C#的一个多线程,需要三步
一、编写入口函数
二、创建入口委托
三、创建线程

编写入口函数

作者认为入口函数就是用户想要用多线程实现的函数,比如运行一台电脑,我们想要下载一个游戏的同时听音乐,下载一个游戏就是一个入口函数,同样听音乐也是一个入口函数,这个入口函数需要用户自己定义及编写

创建入口委托

ThreadStart entryPoint(用户自己定义) = new ThreadStart(入口函数);

创建线程

Thread WorkThread = new Thread(entryPoint);

上面这个是相较于麻烦一点的创建线程的办法,同样可以采用另外一种简便的方法创建线程,使用匿名函数创建线程

Thread 用户自己定义名称  = new Thread(delegate()
{
    入口函数代码
});
 Thread ThreadA = new Thread(delegate ()
            {
                for (int i = 0; i <= 1000; i++)
                {
                    if (i % 10 == 0)
                    {
                        Console.Write('A');
                    }
                }
            });
            Thread ThreadB = new Thread(delegate ()
            {
                for (int i = 0; i < 1000; i++)
                {
                    if (i % 10 == 0)
                    {
                        Console.Write('B');
                    }
                }
            });
            ThreadA.Start();
            ThreadB.Start();

线程的优先级

线程的优先级总共分为五个。普通线程的优先级默认 Normal;高一点的优先级:AboveNormal或Highest;低一点的优先级:BelowNormal或Lowest
优先级的设置代码

线程名称.Priority = ThreadPriority.AboveNormal/Highest/BelowNormal/Lowest;
          //线程A
            Thread ThreadA = new Thread(delegate ()
            {
                for (int i = 0; i <= 100000; i++)
                {
                    if (i % 100 == 0)
                    {
                        Console.Write('A');
                    }
                }
            });
            //线程B
            Thread ThreadB = new Thread(delegate ()
            {
                for (int i = 0; i < 100000; i++)
                {
                    if (i % 100 == 0)
                    {
                        Console.Write('B');
                    }
                }
            });
            //改变线程优先级
            ThreadA.Priority = ThreadPriority.AboveNormal;  //将ThreadA的优先级比Normal高
            ThreadB.Priority = ThreadPriority.BelowNormal;  //将ThreadB的优先级比Normal低
            ThreadA.Start();
            ThreadB.Start();
            //主线程
            for (int i = 0; i < 100000; i++)
            {
                if (i % 100 == 0)
                {
                    Console.Write('C');
                }
            }

运行结果
在这里插入图片描述
系统有限执行优先级较高的线程,但这只意味着优先级较高的线程占有更多的CPU时间,并不意味着一定要先执行完优先级较高的线程,才会执行优先级较低的线程。

线程的插入

线程的插入使用Join()方法,作者认为使用Join方法就是能够将两个交替执行的线程合并为顺序执行的线程,举一个例子,比如我要下两部电影《A》和《B》,我两部先同时下载,但是我突然想先看《A》,我就使用Join方法,先执行下载《A》。等下载完之后在下载《B》

//线程A
            Thread ThreadA = new Thread(delegate ()
            {
                for (int i = 0; i <= 100000; i++)
                {
                    if (i % 100 == 0)
                    {
                        Console.Write('A');
                    }
                }
            });
            //线程B
            Thread ThreadB = new Thread(delegate ()
            {
                for (int i = 0; i < 100000; i++)
                {
                    if (i % 100 == 0)
                    {
                        Console.Write('B');
                    }
                }
                ThreadA.Join();
                for(int i =0;i<100000;i++)
                {
                    if(i%100==0)
                    {
                        Console.Write('b');
                    }
                }
            });
         
            ThreadA.Start();
            ThreadB.Start();

运行结果
在这里插入图片描述

线程的状态

线程有以下几种状态:
Unstarted:线程尚未开始运行
Running:线程正在正常运行
Suspended:线程已经被挂起
SuspendRequested:正在请求挂起线程,但还未来得及响应
WaitSleepJoin:由于调用Wait()、Sleep()、Join()方法而使线程处于阻塞状态
Stopped:线程已经停止
StopRequested:已调用了Abort()方法,但还未收到ThreadAbortException异常
Aborted:线程处于Stopped状态中
Background:线程在后台执行

线程的同步

在引入线程同步概念之前,先引入一个无关线程和相关线程的概念。
无关线程:线程运行的时候相互之间没有任何联系,各自独立运行,互不干扰;
相关线程:线程运行的时候相互之间存在联系,比如一个线程等待另一个线程的运算结果,两个线程共享一个资源等。
临界资源:多个线程共享的资源称为临界资源
临界区:访问临界资源的代码称为临界区

 //缓冲区,只能容纳一个字符
        private static char buffer;
        private static int count;
        static void Main(string[] args)
        {
            //线程:写者
            Thread Writer = new Thread(delegate ()
            {
                string sentence = "这是一个测试多线程同步的例子。";
                count = sentence.Length;
                for (int i = 0; i < sentence.Length; i++)
                {
                    buffer = sentence[i];  //向缓冲区写入数据
                   Thread.Sleep(26);
                }
            });

            //线程:读者
            Thread Reader = new Thread(delegate ()
            {
                for (int i = 0; i < count; i++)
                {
                    char ch = buffer;
                    Console.Write(ch);
                    Thread.Sleep(20);

                }
            });
            Writer.Start();
            Reader.Start();

互锁

上面的例子,在实现过程中,有可能像缓冲区写入数据的时候写了好几次,而读取数据只读了一次,这样就可能造成数据的丢失。为了解决这种情况,引入互锁,作者的理解:我们写一个数据写入到缓冲区,我们先检查一下缓冲区数据是否已经满了,如果满了就等待,将读取数据将缓冲区的数据读取走之后才可以重新写入数据。读取数据之后将缓冲区的数据清零,为了标记缓冲区是否已满,需要引入一个计数器来标记缓冲区。
通过System.Threading命名空间的Interlocked类控制计数器,从而实现进程的同步
Read() :读取计数器的值
Increment() :使计数器增加1
Decrement() :使计数器减小1
Add():使计数器增加指定的数值
Exchange() :把计数器设定为指定的数值
CompareExchange():先把计数器与某个值比较,若相等,则把计数器设定为指定的数值。

//缓冲区,只能容纳一个字符
        private static char buffer;
        private static int count;
        private static long numberOfUsedSpace = 0;  //标识量(缓冲区已使用的空间,初始值为0)
        static void Main(string[] args)
        {
            //线程:写者
            Thread Writer = new Thread(delegate ()
            {
                string sentence = "这是一个测试多线程同步的例子。";
                count = sentence.Length;
                for (int i = 0; i < sentence.Length; i++)
                {
                    while(Interlocked.Read(ref numberOfUsedSpace)==1)  //检查缓冲区是否满了,如果满了,就继续等待
                    {
                        Thread.Sleep(10);
                    }
                    buffer = sentence[i];  //向缓冲区写入数据
                   Thread.Sleep(26);
                   Interlocked.Increment(ref numberOfUsedSpace);  //写入数据后将缓冲区标记为满
                }
            });

            //线程:读者
            Thread Reader = new Thread(delegate ()
            {
                for (int i = 0; i < count; i++)
                {
                    while(Interlocked.Read(ref numberOfUsedSpace)==0)   //检查缓冲区是否为空,如果为空,继续等待
                    {
                        Thread.Sleep(10);
                    }
                    char ch = buffer;
                    Console.Write(ch);
                    Thread.Sleep(20);
                    Interlocked.Decrement(ref numberOfUsedSpace);  //读取数据后将缓冲区标记为空

                }
            });
            Writer.Start();
            Reader.Start();

管程

另外一种线程同步的办法是采用管程(Monitor)的办法。这种办法是采用操作临界资源的办法来实现线程的同步。
理解:以上面程序为例子,我们将临界资源设置一个锁,当写入数据程序想要获取临界资源的时候,先检查临界资源是否有人在用,有过有人用,就继续等待,如果没有人用,写入就获取到临界资源并进行上锁,当读取数据程序想要读取临界资源的数据时候,发现临界资源已经上锁,读取数据线程就进行睡眠,进行等待,之后写入数据使用完之后,会唤醒读取数据线程获取临界资源。
使用管程不用创建对象
Monitor的部分方法:
Enter(): 获取临界资源的独占锁,若不成功,睡眠在临界资源上
TryEnter():试图获取临界资源的独占锁,若不成功,立即返回
Pulse():唤醒睡眠在临界资源上的线程
PulseAll():唤醒睡眠在临界资源上的所有线程
Wait():释放独占锁并让当前线程睡眠在临界资源上
Exit():释放独占锁,退出临界区

  //缓冲区,只能容纳一个字符
        private static char buffer;
        private static int count;
        private static object lockForBuffer = new object();  //用于同步的对象
        static void Main(string[] args)
        {
            //线程:写者
            Thread Writer = new Thread(delegate ()
            {
            string sentence = "这是一个测试多线程同步的例子。";
            count = sentence.Length;
            for (int i = 0; i < sentence.Length; i++)
            {
                try
                {
                    //进入临界区
                    Monitor.Enter(lockForBuffer);
                    //向缓冲区写入数据
                    buffer = sentence[i];
                    //唤醒睡眠在临界资源上的线程
                    Monitor.Pulse(lockForBuffer);
                    //让当前线程睡眠在临界资源上
                    Monitor.Wait(lockForBuffer);
                    }
                catch(System.Threading.ThreadInterruptedException)
                    {
                        Console.WriteLine("线程Writer被中止");
                    }
                finally
                    {
                        //退出临界区
                        Monitor.Exit(lockForBuffer);
                    }
                }
            });

            //线程:读者
            Thread Reader = new Thread(delegate ()
            {
                for (int i = 0; i < count; i++)
                {
                    try
                    {
                        //进入临界区
                        Monitor.Enter(lockForBuffer);
                        //从缓冲区读取数据
                        char ch = buffer;
                        Console.Write(ch);

                        //唤醒睡眠在临界资源上的线程
                        Monitor.Pulse(lockForBuffer);
                        //让当前线程睡眠在临界资源上
                        Monitor.Wait(lockForBuffer);
                    }
                    catch(System.Threading.ThreadInterruptedException)
                    {
                        Console.WriteLine("线程Reader被中止");
                    }
                    finally
                    {
                        //退出临界区
                        Monitor.Exit(lockForBuffer);
                    }

                }
            });
            Writer.Start();
            Reader.Start();

同样可以使用更加简洁的lock语句

lock(缓冲区对象)
{
    //临界区代码
    .........;
    .........;
}
    
 //等同于
 try
 {
  Monitor Enter(要锁定的对象)
  //临界区代码
  ........;
  ........;
 }
 finally
 {
   Monitor.Exit(要锁定的对象);
 }
 //缓冲区,只能容纳一个字符
        private static char buffer;
        private static int count;
        private static object lockForBuffer = new object();  //用于同步的对象
        static void Main(string[] args)
        {
            //线程:写者
            Thread Writer = new Thread(delegate ()
            {
            string sentence = "这是一个测试多线程同步的例子。";
            count = sentence.Length;
                for (int i = 0; i < sentence.Length; i++)
                {
                    lock (lockForBuffer)
                    {
                        buffer = sentence[i];
                        Monitor.Pulse(lockForBuffer);
                        Monitor.Wait(lockForBuffer);
                    }
                }
            });

            //线程:读者
            Thread Reader = new Thread(delegate ()
            {
                for (int i = 0; i < count; i++)
                {
                  lock(lockForBuffer)
                    {
                        char ch = buffer;
                        Console.Write(ch);
                        Monitor.Pulse(lockForBuffer);
                        Monitor.Wait(lockForBuffer);
                    }

                }
            });
            Writer.Start();
            Reader.Start();

互斥体(Mutex类)

许多线程需要共享资源,但是使用资源只需要自己独享。比如打印机每次只能打印一份文件。这种称为互斥。
线程互斥实质上也是同步,可以看作一种特殊的线程同步。线程的互斥常用Mutex类
Mutex的部分方法:
WaitOne() : 请求互斥体的所属权,只有请求到所属权后线程才能进入临界区
ReleaseMutex() :释放互斥体的所属权
OpenExisting():打开现有的已命名互斥体
Mutex类是非静态方法,使用时必须创建一个Mutex对象。
互斥体又两种类型:
局部互斥体:只能在创建它的程序中使用
系统互斥体:能被系统中不同的应用程序共享

//创建互斥体
Mutex fileMutex = new Mutex(false(创建者是否具有初始所属权),"MutexForTimeRecordFile(互斥体的系统名称)")'
//创建第一个程序,在主函数中输入下面代码
using System;
using System.Threading;
using System.IO;


namespace ConsoleApp10
{
    class Program
    {
     
        static void Main(string[] args)
        {
            Thread ThreadA = new Thread(delegate () {
                Mutex fileMutex = new Mutex(false, "MutexForTimeRecordFile"); //创建互斥体
                string fileName = @"C:/ALL_TEST/TimeRecord.txt";
                for (int i = 1; i <= 10; i++)
                {
                    try
                    {
                        fileMutex.WaitOne();  //请求互斥体的所属权,若成功,进入临界区,不成功,等待
                        File.AppendAllText(fileName, "ThreadA:" + DateTime.Now + "\r\n"); //在临界区中操作临界资源,即向文件中写入数据
                    }
                    catch(System.Threading.ThreadInterruptedException)
                    {
                        Console.WriteLine("线程A被中断");
                    }
                    finally
                    {
                        //释放互斥体的所属权
                        fileMutex.ReleaseMutex();
                    }
                    Thread.Sleep(1000);
            }
            });
            ThreadA.Start();
        }

    }
}


//创建第二个程序,在主函数中输入下面代码
using System;
using System.Threading;
using System.IO;


namespace ConsoleApp10
{
    class Program
    {
     
        static void Main(string[] args)
        {
            Thread ThreadB = new Thread(delegate () {
                Mutex fileMutex = new Mutex(false, "MutexForTimeRecordFile"); //创建互斥体
                string fileName = @"C:/ALL_TEST/TimeRecord.txt";
                for (int i = 1; i <= 10; i++)
                {
                    try
                    {
                        fileMutex.WaitOne();  //请求互斥体的所属权,若成功,进入临界区,不成功,等待
                        File.AppendAllText(fileName, "ThreadB:" + DateTime.Now + "\r\n"); //在临界区中操作临界资源,即向文件中写入数据
                    }
                    catch(System.Threading.ThreadInterruptedException)
                    {
                        Console.WriteLine("线程A被中断");
                    }
                    finally
                    {
                        //释放互斥体的所属权
                        fileMutex.ReleaseMutex();
                    }
                    Thread.Sleep(1000);
            }
            });
            ThreadB.Start();
        }

    }
}

运行结果:
在这里插入图片描述

死锁

用一个例子就可以讲解死锁的概念:一对情侣去一个餐厅吃饭,但是餐厅只剩下一个刀子和一个叉子给他们用,只有同时有刀子和叉子才可以吃饭。
不死锁的情况:男生拿起刀子和叉子,吃点;然后同时放下刀子和叉子,女孩拿起刀子和叉子,吃点;放下刀子和叉子,男生拿起刀子和叉子…
死锁的情况:男生拿起刀子,女生拿起叉子。男生等叉子,女生等刀子,一直等等…死锁了~

线程池

通过Thread类创建的线程并销毁线程代价相对于来说很高,过多的线程会消耗掉大量的内存和CPU资源。改善这种状况,引入线程池概念。程序中包含若干个简单的且不需要特殊控制的线程,可以使用线程池(ThreadPool)
ThreadPool使用方法:
GetMaxThreads():获取线程池中线程数目的上限
GetMinThreads():获取线程池中线程数目的下限
SetMaxThreads():设置线程池中线程数目的上限
SetMinThreads():设置线程池中线程数目的下限
QueueUserWorkItem():将工作任务排入线程池

using System;
using System.Threading;


namespace ConsoleApp11
{
    class Program
    {
        //用于记录已经运行完毕的线程数目
        static int finishedThreadCount = 0;
        //用来保存每个线程的计算结果
        static int[] result = new int[10];
        static void Main(string[] args)
        {
            //向线程池中排入9个工作线程
            for(int i =1;i<=9;i++)
            {
                ThreadPool.QueueUserWorkItem(new WaitCallback(WorkFunction), i);
            }
            //等待9个工作线程运行完毕(注意,这是个空循环)
            while (finishedThreadCount < 9) ;

                //输出计算结果
                for (int i = 0; i <= 9; i++)
                {
                    Console.WriteLine("线程{0}:{0}!={1}", i, result[i]);
                }
        }
        //工作函数 :求n的阶乘
        public static void WorkFunction(object n )
        {
            //计算阶乘
            int fac = 1;
            for(int i =1;i<=(int)n;i++)
            {
                fac *= i;
            }
            result[(int)n] = fac;
            finishedThreadCount++;
        }
    }
}

以下几种情况不适合使用ThreadPool而使用单独的Thread
1.线程执行需要很长时间
2.需要为线程指定详细的优先级
3.在执行过程中需要对线程进行操作,比如睡眠、挂起等。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值