C#多线程编程

多线程的概念看:VC API常用函数简单例子大全四-CSDN博客里的第三十九个函数。

现在直接看个例子,一个C#多线程的例子:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ThreadConls
{
    class Program
    {
        static void Main(string[] args)
        {
            Program prg = new Program();
            //线程的执行方法
            System.Threading.ThreadStart tdStart = new System.Threading.ThreadStart(prg.OutNumber);
            //创建一个线程对象
            System.Threading.Thread thread =  new System.Threading.Thread(tdStart);
            //启动线程
            thread.Start();
            for (int i = 0; i < 10; i++)
            {
                //主线程睡眠500毫秒
                System.Threading.Thread.Sleep(500);
                Console.WriteLine("Main:{0}", i);
            }
        }
        //线程函数
        public void OutNumber()
        {
            for (int i = 10; i > 0; i--)
            {
                Console.WriteLine("OutNumber:{0}", i);
                //这个线程睡眠500毫秒
                System.Threading.Thread.Sleep(500);
            }
        }
    }
}

Thread.Sleep()是使线程休眠的方法,如果它的参数为System.Threading.Timeout.Infinite将会使线程无限期的休眠下去,直到其它线程调用Interrupt方法,比如在主函数中调用thread.Interrupt();而  thread.Abort();方法可以使线程终止。

上面的的使线程休眠方法,只能是线程自己调用。也就是说,一个线程无法调用Sleep方法使另一个线程睡眠。

而  thread.Suspend();方法则没这个限制,当然这个方法只能使线程挂起,它不指定挂起多少时间。一个线程可以调用thread.Suspend();方法使另一个线程暂停。调用 thread.Resume()方法可以使唤醒线程使线程继续执行。thread.Resume()对应着thread.Suspend()方法

线程同步

     为什么要线程同步呢?比如一个线程正读取一个文件,而另一个线程正好往这个文件里写入数据,这样就容易发生错误,得不到正确的数据。那么变量也是一样,一个线程在修改变量的值,而另一个线程正读取这个变量。这样读取的变量值就不具有唯一性,可能是修改前的值,有可能是修改后的值。为了防止这种情况发生。就需要线程同步了。

从某一方面来说,线程同步禁止了多线程的功能,使多线程的优势丧失了。如非必要,不要使用。

看一个没有使用线程同步的例子:按道理我输出的数永远不会为负数。但结果不是。其实也看得出来是什么原因。

我只是故意找了一种这样的情况。一个简单的示例。说明线程同步的用处。不使用线程同步,会使你的判断出错,根据num变量。

代码示例:

    class Program
    {
        public static int num = 0;
        static void Main(string[] args)
        {
            //创建一个线程对象
            System.Threading.Thread thread = new System.Threading.Thread(OutNumber);
            //启动线程,执行线程函数
            thread.Start();
            Random ram = new Random();
            while (true)
            {
                //产生-100至100以内的随机数
                num = ram.Next(-100, 100);
                //睡眠100毫秒
                System.Threading.Thread.Sleep(100);
            }
        }
        public static void OutNumber()
        {
            while (true)
            {
                //num大于0才输出
                if (num > 0)
                {
                    //睡眠200毫秒
                    System.Threading.Thread.Sleep(200);
                    Console.WriteLine(num);
                }
            }
       
        }
    }

 使用ManualResetEvent类可以解决上面的问题,ManualResetEvent类的构造函数的参数为true,则表明这个对象初始为有信号状态。

为false无信号。

ManualResetEvent类的WaitOne();可以阻塞当前线程,依据ManualResetEvent对象当前有无信号。

也就是说,当调用WaitOne函数的对象没有信号时,WaitOne函数就会等待,一直到有信号。

而ManualResetEvent类的Set和Reset可以设置对象有无信号,Set设置为有信号。Reset设置为无信号。

看使用了ManualResetEvent类的例子,避免了输出负数:

    class Program
    {

      //创建ManualResetEvent对象
        public static System.Threading.ManualResetEvent restEvent = new System.Threading.ManualResetEvent(false);
        public static int num = 0;
        static void Main(string[] args)
        {
            //创建一个线程对象
            System.Threading.Thread thread = new System.Threading.Thread(OutNumber);
            //启动线程,执行线程函数
            thread.Start();
            Random ram = new Random();
            while (true)
            {
                restEvent.WaitOne();//等待信号来临
                //产生-100至100以内的随机数
                num = ram.Next(-100, 100);
                //睡眠100毫秒
                System.Threading.Thread.Sleep(100);
            }
        }
        public static void OutNumber()
        {
            while (true)
            {
                //设置为无信号
                restEvent.Reset();
                //num大于0才输出
                if (num > 0)
                {
                    //睡眠200毫秒
                    System.Threading.Thread.Sleep(200);
                    Console.WriteLine(num);
                }
                //设置为有信号
                restEvent.Set();
            }

        }
    }

另还有相关的类AutoResetEvent

接下来看另一种需要用到线程同步的情况,多个线程执行同一个函数。看下面例子:

    class Program
    {
        static void Main(String[] args)
        {
           //创建两个线程对象,执行的都是threadFun函数
            System.Threading.Thread thread1 = new System.Threading.Thread(threadFun);
            System.Threading.Thread thread2 = new System.Threading.Thread(threadFun);
            thread1.Start();
            thread2.Start();
            Console.ReadLine();
        }
        public static void threadFun()
        {
            for (int i = 0; i < 10; i++)
            {
                Console.WriteLine("线程ID号为{0}的输出:{1}",
                    System.Threading.Thread.CurrentThread.ManagedThreadId, i);
                System.Threading.Thread.Sleep(100);
            }

            //做一些其它事.......
        }
    }

我也不想举一些实际应用中的例子了,比如例子中有一个什么问题,然后用通过解决这个问题的方式来学习,或者引出一些知识点。

这里我举的例子,仅仅说明一些规则,比如Monitor类能保证一段代码在同一时间只能被一个线程执行。

另:上面那个输出负数的问题,也可以用Monitor类来解决。而且使用更方便。

看下例:

    class Program
    {
        private static Object obj = new Object();
        static void Main(String[] args)
        {
           //创建两个线程对象
            System.Threading.Thread thread1 = new System.Threading.Thread(threadFun);
            System.Threading.Thread thread2 = new System.Threading.Thread(threadFun);
            thread1.Start();
            thread2.Start();
            Console.ReadLine();
        }
        public static void threadFun()
        {

          //锁定对象
            System.Threading.Monitor.Enter(obj);
            for (int i = 0; i < 10; i++)
            {
                Console.WriteLine("线程ID号为{0}的输出:{1}",
                    System.Threading.Thread.CurrentThread.ManagedThreadId, i);
                System.Threading.Thread.Sleep(100);
            }

         //解锁
            System.Threading.Monitor.Exit(obj);
            //做一些其它事.......
        }
    }

Monitor.Enter锁定一个对象,当对象被锁定后,别的线程,再调用Monitor.Enter锁定同样的对象,就会被阻塞,因为对象已经被锁定了。

只能等锁定对象的线程调用Monitor.Exit(obj);解锁对象后,别的线程才可以接着锁定,进入里面执行代码。这样就可以保证包含在这两个函数的之间代码,在同一时间只能被一个线程执行。

而这个锁定对象只是一个标记,用这个对象作为可不可以执行代码的标记。

 另外跟Monitor.Enter和Monitor.Exit类似的还有lock,这个lock实际上是对前者的一个包装。

比如一个函数里的代码段如下:

lock(obj)  //锁定obj对象,其后大括号中的代码在同一时间只能被一个线程访问。

{

  for (int i = 0; i < 10; i++)
            {
                Console.WriteLine("线程ID号为{0}的输出:{1}",
                    System.Threading.Thread.CurrentThread.ManagedThreadId, i);
                System.Threading.Thread.Sleep(100);
            }

}

上面的代码跟下面这个是一样的。

 //锁定对象
System.Threading.Monitor.Enter(obj);
for (int i = 0; i < 10; i++)
{
  Console.WriteLine("线程ID号为{0}的输出:{1}",
 System.Threading.Thread.CurrentThread.ManagedThreadId, i);
 System.Threading.Thread.Sleep(100);
}

 //解锁
System.Threading.Monitor.Exit(obj);

需要了解的一些东西:

如果有三个线程执行了lock(obj);按执行lock(obj);的顺序,我们把这三个线程取名为第一线程,第二线程,第三线程。

当第一线程最先锁定了obj后,第二线程执行lock(obj);会发现obj已经被锁定了,那么这个线程就会到“就绪队列”中去,等待着obj被解锁。只要obj一被解锁,就轮到它来锁定对象,执行代码了。第三个线程也是如此(等待着第二个线程解锁obj)。

 按照执行lock(obj)的先后顺序,就绪队列中的线程也是有个先后顺序,它们的顺序跟执行lock(obj)的顺序一致。然后一个个按顺序等待着锁定obj,执行lock对应的代码。

“就绪队列”对应的还有个“等待队列”。等待队列是比就绪队列低一级的,等待队列中的线程,它们是等待着进入就绪列队,而就绪队列就是等待着锁定对象,执行代码。

什么情况下,线程会进入等待队列呢,调用Monitor.Wait方法,当然并不是任何线程都可以调用Monitor.Wait方法的,它只能是获得对象锁的线程。

为什么会有这个限制,看一下Monitor.Wait它的作用就知道了,Wait使当前线程放弃对象锁的拥有权,相当于临时解锁了(Monitor.Exit)。

并且使当前线程进入等待队列,阻塞当前线程。等待队列中的线程顺序跟线程调用Monitor.Wait先后顺序一致。就如同就绪队列一样。

但是如何使处于等待队列中的第一个线程,进入就绪队列呢。像就绪队列获得对象锁,只要另一个拥有对象锁的线程调用Monitor.Exit

释放就可以了。(lock语句执行完了,它暗地里也会调用的)。

使等待队列中的线程进入就绪队列中,有一个专门的函数可以做到。Monitor.Pluse函数。每调用一次,就可以等待队列中排在第一的线程进入就绪队列。当它再次拥有对象锁的时候,它就会从Monitor.Wait处开始执行。

而Monitor.PluseAll可以使等待队列中的所有线程进入就绪队列。

看下面这个例子就明白了:

    class Program
    {
        private static Object obj = new Object();
        static void Main(String[] args)
        {
           //创建两个线程对象
            System.Threading.Thread thread1 = new System.Threading.Thread(thread1Fun);
            System.Threading.Thread thread2 = new System.Threading.Thread(thread2Fun);
            thread1.Start();
            thread2.Start();
            Console.ReadLine();
        }
        public static void thread1Fun()
        {
            //锁住obj
            lock (obj)
            {
                Console.WriteLine("这是thread1的输出!");
                //临时解锁,并阻塞当前线程,直到再次获得对象锁。
                System.Threading.Monitor.Wait(obj);
                Console.WriteLine("thread1获得对象锁了!");

            }
          
            //做一些其它事.......
        }
        public static void thread2Fun()
        {
            lock (obj)
            {
                Console.WriteLine("我有机会输出了,是thread1临时解锁了!");
                //通知thread1(队列第一个),我要解锁了,你可以进入就绪队列
                System.Threading.Monitor.Pulse(obj);
            }
            //做一些其它事
        }
    }

可以做个测试,如果把System.Threading.Monitor.Pulse(obj);这句删了,那么Console.WriteLine("thread1获得对象锁了!");就永远不会执行。因为这个线程调用了System.Threading.Monitor.Wait(obj);进入了等待队列中后,没有其它线程通知它进入就绪队列。

那么即使对象解锁了,它也拥有不了对象锁。它一直处于等待队列中等待。

而从("thread1获得对象锁了!");这个字符串在("我有机会输出了,是thread1临时解锁了!");之后输出就可以看得出。

线程1临时解锁了,让线程2有机会输出字符串了,然后线程2再调用Pulse通知线程1进入就绪队列中。这样线程1才能输出第二个字符串。

线程池

直接看一个简单的例子吧,内地里也是创建了线程。像是封装了。

    class Program
    {
       
        static void Main(String[] args)
        {
            System.Threading.ThreadPool.QueueUserWorkItem(OutNumber1, 10);
            System.Threading.ThreadPool.QueueUserWorkItem(OutNumber2, 15);
            Console.ReadLine();
        }
        static void OutNumber1(object obj)
        {
            int num = (int)obj;
            for (int i = 0; i < num; i++)
            {
                Console.WriteLine("线程ID{0}的输出:{1}", System.Threading.Thread.CurrentThread.ManagedThreadId, i);
                System.Threading.Thread.Sleep(150);
            }

        }
        static void OutNumber2(object obj)
        {
            int num = (int)obj;
            for (int i = 0; i < num; i++)
            {
                Console.WriteLine("线程ID{0}的输出:{1}", System.Threading.Thread.CurrentThread.ManagedThreadId,i);
                System.Threading.Thread.Sleep(150);
            }
        }

    }

线程池更进一步的了解参考其它资料吧。

定时器的例子:

    class Program
    {
      public static int time = 0;
      static void Main(String[] args)
        { 

        //每隔1秒执行一次OutTime函数
          System.Threading.Timer timer = new System.Threading.Timer(OutTime, 0, 0, 1000);
          Console.ReadLine();
        }
      static void OutTime(Object obj)
      {
          time++;
          Console.WriteLine("已过去了{0}秒", time);
      }

 }

Timer构造函数,第一个参数指定要执行的函数名,第二个是传进去的对象,对应着执行函数的Object参数。可以自行定义。

第三个是创建好的定时器后,延迟多久执行函数。最后一个参数是每隔多久执行一次OutTime函数。

  • 20
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Bczheng1

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值