服务器之家:专注于VPS、云服务器配置技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - C# - C# 线程同步的方法

C# 线程同步的方法

2022-09-29 16:18冬冬他哥哥 C#

这篇文章主要介绍了C# 线程同步的方法,文中讲解非常细致,代码帮助大家更好的理解和学习,感兴趣的朋友可以了解下

一、进程内部的线程同步

1、使用lock,用法如下:

?
1
2
3
4
5
6
7
8
9
private static readonly object SeqLock = new object();
 
    private void Print()
    {
      lock (SeqLock)
      {
        Console.WriteLine("test");
      }
    }

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

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

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
//缓冲区,只能容纳一个字符
   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相同

?
1
2
3
4
5
Monitor.Enter(obj){
  //Synchronized part
}finally{
  Monitor.Exit(obj);
}

TryEnter则可设置等待时间等

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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() 解除锁定。构造函数使用如下:

?
1
2
bool isNew = false;
mutex = new Mutex(false, "Mutex1", out isNew);

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

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

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

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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: 信号量的作用于互斥锁类似,但它可以定义一定数量的线程同时使用。下面是构造函数:

?
1
2
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() 中,当线程获取信号量锁,释放锁或等待超时,都会在控制台里输出,

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
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(),

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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()才能切换到非终止模式。

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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# 线程同步的资料请关注服务器之家其它相关文章!

原文链接:https://www.cnblogs.com/xietianjiao/p/13386373.html

延伸 · 阅读

精彩推荐
  • C#C#控制台基础 List泛型集合与对应的数组相互转换实现代码

    C#控制台基础 List泛型集合与对应的数组相互转换实现代码

    这篇文章主要介绍了C#控制台基础 List泛型集合与对应的数组相互转换实现代码,需要的朋友可以参考下...

    C#教程网9612021-12-14
  • C#C#用递归算法解决经典背包问题

    C#用递归算法解决经典背包问题

    背包问题有好多版本,本文只研究0/1版本,即对一个物体要么选用,要么就抛弃,不能将一个物体再继续细分的情况。...

    张玉彬9132021-11-25
  • C#C#正则表达式转义字符介绍

    C#正则表达式转义字符介绍

    正则表达式,又称正规表示法、常规表示法。这篇文章主要介绍了C#正则表达式转义字符介绍的相关资料,非常不错,具有参考借鉴价值,需要的朋友可以参...

    27780850016562021-12-01
  • C#C# MVC模式下商品抽奖功能实现

    C# MVC模式下商品抽奖功能实现

    这篇文章主要为大家分享了C#在MVC模式下实现商品抽奖功能,思路清晰,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...

    熊灬孩灬子10162021-11-19
  • C#C#实现String类型和json之间的相互转换功能示例

    C#实现String类型和json之间的相互转换功能示例

    这篇文章主要介绍了C#实现String类型和json之间的相互转换功能,涉及C# json格式数据的构造、转换相关操作技巧,需要的朋友可以参考下...

    xy_int4172022-01-21
  • C#C#中字符串编码处理

    C#中字符串编码处理

    C#中字符串编码处理,需要的朋友可以参考一下...

    C#菜鸟教程4612020-12-19
  • C#Unity3D UGUI实现缩放循环拖动卡牌展示效果

    Unity3D UGUI实现缩放循环拖动卡牌展示效果

    这篇文章主要为大家详细介绍了Unity3D UGUI实现缩放循环拖动展示卡牌效果,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参...

    诗远3692022-03-11
  • C#C#代码实现PDF文档操作类

    C#代码实现PDF文档操作类

    本篇文章给大家介绍使用pdf文档操作C#代码,本文代码非常简单,代码附有注释,需要注意的是:需要添加itextsharp.dll引用才可以正常通过编译,感兴趣的朋...

    C#教程网3812021-11-01