欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

C# 线程同步的方法

程序员文章站 2022-07-04 21:41:32
一、进程内部的线程同步1、使用lock,用法如下:private static readonly object seqlock = new object(); private void prin...

一、进程内部的线程同步

1、使用lock,用法如下:

private static readonly object seqlock = new object();

    private void print()
    {
      lock (seqlock)
      {
        console.writeline("test");
      }
    }

特性:只能传递对象,无法设置等待超时

2、使用:interlocked(原子操作)

其在system.threading命名空间下,interlocked实际是类控制计数器,从而实现进程的同步,其很容易实现生产者消费者模型

//缓冲区,只能容纳一个字符
   private static char buffer;
   //标识量(缓冲区中已使用的空间,初始值为0)
   private static long numberofusedspace = 0;
   static void main(string[] args)
   {
    //线程:写入者
    thread writer = new thread(delegate ()
    {
     string str = "这里面的字会一个一个读取出来,一个都不会少,,,";
     for (int i = 0; i < 24; i++)
     {
      //写入数据前检查缓冲区是否已满
      //如果已满,就进行等待,直到缓冲区中的数据被进程reader读取为止
      while (interlocked.read(ref numberofusedspace) == 1)
      {
       thread.sleep(50);
      }
      buffer = str[i]; //向缓冲区写入数据
      //写入数据后把缓冲区标记为满(由0变为1)
      interlocked.increment(ref numberofusedspace);
     }
    });
    //线程:读出者
    thread reader = new thread(delegate ()
    {
     for (int i = 0; i < 24; i++)
     {
      //读取数据前检查缓冲区是否为空
      //如果为空,就进行等待,直到进程writer向缓冲区中写入数据为止
      while (interlocked.read(ref numberofusedspace) == 0)
      {
       thread.sleep(50);
      }
      char ch = buffer;  //从缓冲区读取数据
      console.write(ch);
      interlocked.decrement(ref numberofusedspace);
     }
    });
    //启动线程
    writer.start();
    reader.start();
    console.readkey();

3、使用monitor

其中monitor.enter()和lock相同

      monitor.enter(obj){
        //synchronized part
      }finally{
        monitor.exit(obj);
      }

tryenter则可设置等待时间等

bool locktaken=false;
      monitor.tryenter(obj, 500, ref locktaken);
      if(locktaken){
        try
        {
          //synchronized part
        }
        finally
        {
          monitor.exit(obj);
        }
      }else{
        //don't aquire the lock, excute other parts
      }

二、进程间的同步

1. waithandle:

封装等待对共享资源进行独占访问的操作系统特定的对象。 waithandle:是一个抽象类,我们一般不直接用,而是用它的派生类:

autoresetevent、eventwaithandle、manualresetevent、mutex、semaphore

这个抽象类的方法如下:

waitone(): 等待一个信号的出现,可设置超时;

waitall(): 等待多个信号的出现,可设置超时;

waitany(): 等待任意一个信号的出现,可设置超时;

2、mutex: 与monitor 类似,只有一个线程能够获取锁定。利用waitone() 获取锁定,利用releasemutex() 解除锁定。构造函数使用如下:

      bool isnew = false;
      mutex = new mutex(false, "mutex1", out isnew);

参数1:锁创建后是否由主调线程拥有。 如果设为true,相当于调用了waitone(),需要释放,否则其他线程无法获取锁;

参数2:锁名称,可通过openexist()或tryopenexist() 打开已有锁,因为操作系统识别有名称的互锁,所以可由不同的进程共享。若锁名称为空,就是未命名的互锁,不能在多个进程之间共享;

参数3:  是否为新创建的互锁;

下面的例子演示mutex 在进程之间的使用:    class program

private static mutex mutex = null; 
    static void main(string[] args)
    {
      bool isnew = false;
      mutex = new mutex(false, "mutex1", out isnew);
      console.writeline("main start....");
      mutex.waitone();
      console.writeline("aquire lock and running....");
      thread.sleep(10000);
      mutex.releasemutex();
      console.writeline("release lock....");
      console.writeline("main end....");
      console.readline();
    }
  }

连续2次运行这个控制台程序的exe,结果如下,首先运行的获取 mutex1 互锁, 后面运行的会等待直到前面运行的释放 mutex1 互锁。

 3.semaphore: 信号量的作用于互斥锁类似,但它可以定义一定数量的线程同时使用。下面是构造函数:

      bool isnew = false;
      semaphore = new semaphore(3, 3, "semaphore1", out isnew);

参数1:创建后,最初释放的锁的数量,如参数1设为2,参数2设为3,则创建后只有2个锁可用,另1个已经锁定;

参数2:定义可用锁的数量;

参数3:  信号量的名称,与mutex类似;

参数4:是否为新创建的互锁;

以下例子创建了信号量“semaphore1”,利用parallel.for() 同步运行func1() ,在func1() 中,当线程获取信号量锁,释放锁或等待超时,都会在控制台里输出,

class program
  {
    private static semaphore semaphore = null;
    static void main(string[] args)
    {

      console.writeline("main start....");
      bool isnew = false;
      semaphore = new semaphore(3, 3, "semaphore1", out isnew);
      parallel.for(0, 6, func1);
      console.writeline("main end....");
      console.readline();
    }

    static void func1(int index)
    {
      console.writeline("task {0} start....",task.currentid);
      bool iscomplete = false;
      while (!iscomplete)
      {
        if (semaphore.waitone(1000))  
        {
          try
          {
            console.writeline("task {0} aquire lock....", task.currentid);
            thread.sleep(5000);
          }
          finally
          {
            semaphore.release();
            console.writeline("task {0} release lock....", task.currentid);
            iscomplete = true;
          }
        }
        else
        {
          console.writeline("task {0} timeout....", task.currentid);
        }
      }
    }

运行结果如下,线程1,2,3首先获取信号量锁,线程4,5,6在等待,直到1,2,3释放,

4. autoresetevent 类:

可以使用事件通知其他任务,构造函数为 public autoresetevent(bool initialstate)。

当initialstate=true,处于signaled 模式(终止状态),调用waitone() 也不会阻塞任务,等待信号,调用reset()方法,可以设置为non-signaled 模式;

当initialstate=fasle,处于non-signaled 模式(非终止状态),调用waitone() 会等待信号阻塞当前线程(可以在多个线程中调用,同时阻塞多个线程),直到调用set()发送信号释放线程(调用一次,只能释放一个线程),一般使用这种方式;

以下例子创建5个任务,分别调用waitone()阻塞线程,接着每隔2s 调用set(),

private static autoresetevent autoreset = new autoresetevent(false);
    static void main(string[] args)
    {
      console.writeline("main start....");
      for (int i = 0; i < 5; i++)
      {
        task.factory.startnew(() =>
        {
          console.writeline("{0} start....", task.currentid);
          autoreset.waitone();
          console.writeline("{0} continue....", task.currentid);
        });
      }
      for (int i = 0; i < 5;i++ )
      {
        thread.sleep(2000);
        autoreset.set();
      }
      console.writeline("main end....");
      console.readline();
    }

运行结果每次顺序略有不同,释放是随机的:

5. manualresetevent 类:功能基本上和autosetevent类似,但又一个不同点:

使用autosetevent,每次调用set(),切换到终止模式,只能释放一个waitone(),便会自动切换到非终止模式;但manualresetevent,调用set(),切换到终止模式,可以释放当前所有的waitone(),需要手动调用reset()才能切换到非终止模式。

以下例子说明了这个不同的:

private static manualresetevent manualreset = new manualresetevent(false);
    static void main(string[] args)
    {
      console.writeline("main start....");
      for (int i = 0; i < 5; i++)
      {
        task.factory.startnew(() =>
        {
          console.writeline("{0} start....", task.currentid);
          manualreset.waitone();
          console.writeline("{0} continue....", task.currentid);
        });
      }
      thread.sleep(2000);
      manualreset.set();
      manualreset.waitone();
      console.writeline("it doesn't work now, main continue....");
      manualreset.reset();
      manualreset.waitone();
      console.writeline("main end....");
      console.readline();
    }

以上就是c# 线程同步的方法的详细内容,更多关于c# 线程同步的资料请关注其它相关文章!

相关标签: c# 线程 同步