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

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

服务器之家 - 编程语言 - C# - C#多线程系列之多线程锁lock和Monitor

C#多线程系列之多线程锁lock和Monitor

2022-12-26 14:16痴者工良 C#

这篇文章介绍了C#多线程锁lock和Monitor的用法,文中通过示例代码介绍的非常详细。对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

1,Lock

lock 用于读一个引用类型进行加锁,同一时刻内只有一个线程能够访问此对象。lock 是语法糖,是通过 Monitor 来实现的。

Lock 锁定的对象,应该是静态的引用类型(字符串除外)。

实际上字符串也可以作为锁的对象使用,只是由于字符串对象的特殊性,可能会造成不同位置的不同线程冲突。
如果你能保证字符串的唯一性,例如 Guid 生成的字符串,也是可以作为锁的对象使用的(但不建议)。 
锁的对象也不一定要静态才行,也可以通过类实例的成员变量,作为锁对象。

lock 原型

lock 是 Monitor 的语法糖,生成的代码对比:

?
1
2
3
4
lock (x)
{
    // Your code...
}
?
1
2
3
4
5
6
7
8
9
10
11
object __lockObj = x;
bool __lockWasTaken = false;
try
{
    System.Threading.Monitor.Enter(__lockObj, ref __lockWasTaken);
    // Your code...
}
finally
{
    if (__lockWasTaken) System.Threading.Monitor.Exit(__lockObj);
}

这里先不理会 Monitor,后面再说。

lock 编写实例

首先,如果像下面这样写的话,拉出去打 si 吧。

?
1
2
3
4
5
6
7
8
public void MyLock()
{
    object o = new object();
    lock (o)
    {
    //
    }
}

下面编写一个简单的锁,示例如下:

?
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
44
45
46
class Program
{
    private static object obj = new object();
    private static int sum = 0;
    static void Main(string[] args)
    {
 
        Thread thread1 = new Thread(Sum1);
        thread1.Start();
        Thread thread2 = new Thread(Sum2);
        thread2.Start();
        while (true)
        {
            Console.WriteLine($"{DateTime.Now.ToString()}:" + sum);
            Thread.Sleep(TimeSpan.FromSeconds(1));
        }
    }
 
    public static void Sum1()
    {
        sum = 0;
        lock (obj)
        {
            for (int i = 0; i < 10; i++)
            {
                sum += i;
                Console.WriteLine("Sum1");
                Thread.Sleep(TimeSpan.FromSeconds(2));
            }
        }
    }
 
    public static void Sum2()
    {
        sum = 0;
        lock (obj)
        {
            for (int i = 0; i < 10; i++)
            {
                sum += 1;
                Console.WriteLine("Sum2");
                Thread.Sleep(TimeSpan.FromSeconds(2));
            }
        }
    }
}

类将自己设置为锁, 这可以防止恶意代码对公共对象采用做锁。

例如:

?
1
2
3
4
public void Access()
  {
      lock(this) {}
   }

锁可以阻止其它线程执行锁块(lock(o){})中的代码,当锁定时,其它线程必须等待锁中的线程执行完成并释放锁。但是这可能会给程序带来性能影响。
锁不太适合I/O场景,例如文件I/O,繁杂的计算或者操作比较持久的过程,会给程序带来很大的性能损失。

10 种优化锁的性能方法: http://www.thinkingparallel.com/2007/07/31/10-ways-to-reduce-lock-contention-in-threaded-programs/

2,Monitor

此对象提供同步访问对象的机制;Monotor 是一个静态类型,其方法比较少,常用方法如下:

操作 说明
Enter, TryEnter 获取对象的锁。 此操作还标记关键节的开头。 其他任何线程都不能输入临界区,除非它使用不同的锁定对象执行临界区中的说明。
Wait 释放对象的锁,以允许其他线程锁定并访问对象。 调用线程会等待另一个线程访问对象。 使用脉冲信号通知等待线程关于对象状态的更改。
Pulse 、PulseAll 将信号发送到一个或多个等待线程。 信号通知等待线程:锁定对象的状态已更改,锁的所有者已准备好释放该锁。 正在等待的线程置于对象的就绪队列中,因此它可能最终接收对象的锁。 线程锁定后,它可以检查对象的新状态,以查看是否已达到所需的状态。
Exit 释放对象的锁。 此操作还标记受锁定对象保护的临界区的结尾。

怎么用呢

下面是一个很简单的示例:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private static object obj = new object();
private static bool acquiredLock = false;
 
public static void Test()
{
    try
    {
        Monitor.Enter(obj, ref acquiredLock);
    }
    catch { }
    finally
    {
        if (acquiredLock)
            Monitor.Exit(obj);
    }
}

Monitor.Enter 锁定 obj 这个对象,并且设置 acquiredLock 为 true,告诉别人 obj 已经被锁定。

最后结束时,判断 acquiredLock ,释放锁,并设置 acquiredLock 为 false。

解释一下

临界区:指被某些符号包围的范围。例如 {} 内。

Monitor 对象的 Enter 和 Exit 方法来标记临界区的开头和结尾。

Enter() 方法获取锁后,能够保证只有单个线程能够使用临界区中的代码。使用 Monitor 类,最好搭配 try{...}catch{...}finally{...} 来使用,因为如果获取到锁但是没有释放锁的话,会导致其它线程无限阻塞,即发生死锁。

一般来说,lock 关键字够用了。

示例

下面示范了多个线程如何使用 Monitor 来实现锁:

?
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
44
45
46
47
48
49
50
51
private static object obj = new object();
 private static bool acquiredLock = false;
 static void Main(string[] args)
 {
     new Thread(Test1).Start();
     Thread.Sleep(1000);
     new Thread(Test2).Start();
 }
 
 public static void Test1()
 {
     try
     {
         Monitor.Enter(obj, ref acquiredLock);
         for (int i = 0; i < 10; i++)
         {
             Console.WriteLine("Test1正在锁定资源");
             Thread.Sleep(1000);
         }
 
     }
     catch { }
     finally
     {
         if (acquiredLock)
             Monitor.Exit(obj);
         Console.WriteLine("Test1已经释放资源");
     }
 }
 public static void Test2()
 {
     bool isGetLock = false;
     Monitor.Enter(obj);
     try
     {
         Monitor.Enter(obj, ref acquiredLock);
         for (int i = 0; i < 10; i++)
         {
             Console.WriteLine("Test2正在锁定资源");
             Thread.Sleep(1000);
         }
 
     }
     catch { }
     finally
     {
         if (acquiredLock)
             Monitor.Exit(obj);
         Console.WriteLine("Test2已经释放资源");
     }
 }

设置获取锁的时效

如果对象已经被锁定,另一个线程使用 Monitor.Enter 对象,就会一直等待另一个线程解除锁定。

但是,如果一个线程发生问题或者出现死锁的情况,锁一直被锁定呢?或者线程具有时效性,超过一段时间不执行,已经没有了意义呢?

我们可以通过 Monitor.TryEnter() 来设置等待时间,超过一段时间后,如果锁还没有释放,就会返回 false。

改造上面的示例如下:

?
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
44
45
46
47
48
49
50
51
52
53
54
private static object obj = new object();
private static bool acquiredLock = false;
static void Main(string[] args)
{
    new Thread(Test1).Start();
    Thread.Sleep(1000);
    new Thread(Test2).Start();
}
 
public static void Test1()
{
    try
    {
        Monitor.Enter(obj, ref acquiredLock);
        for (int i = 0; i < 10; i++)
        {
            Console.WriteLine("Test1正在锁定资源");
            Thread.Sleep(1000);
        }
    }
    catch { }
    finally
    {
        if (acquiredLock)
            Monitor.Exit(obj);
        Console.WriteLine("Test1已经释放资源");
    }
}
public static void Test2()
{
    bool isGetLock = false;
    isGetLock = Monitor.TryEnter(obj, 500);
    if (isGetLock == false)
    {
        Console.WriteLine("锁还没有释放,我不干活了");
        return;
    }
    try
    {
        Monitor.Enter(obj, ref acquiredLock);
        for (int i = 0; i < 10; i++)
        {
            Console.WriteLine("Test2正在锁定资源");
            Thread.Sleep(1000);
        }
    }
    catch { }
    finally
    {
        if (acquiredLock)
            Monitor.Exit(obj);
        Console.WriteLine("Test2已经释放资源");
    }
}

到此这篇关于C#多线程系列之多线程锁lock和Monitor的文章就介绍到这了。希望对大家的学习有所帮助,也希望大家多多支持服务器之家。

原文链接:https://www.cnblogs.com/whuanle/p/12722853.html

延伸 · 阅读

精彩推荐
  • C#适合初学者开发的C#在线英汉词典小程序

    适合初学者开发的C#在线英汉词典小程序

    这篇文章主要为大家详细介绍了适合初学者开发的C#在线英汉词典小程序,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...

    毕毕love丹丹7152021-12-07
  • C#C#编程实现取整和取余的方法

    C#编程实现取整和取余的方法

    这篇文章主要介绍了C#编程实现取整和取余的方法,结合实例形式分析了C#中Math.Celling与Math.Floor函数的相关使用技巧,具有一定参考借鉴价值,需要的朋友可以...

    清风远行8142021-11-03
  • C#浅谈C# StringBuilder内存碎片对性能的影响

    浅谈C# StringBuilder内存碎片对性能的影响

    这篇文章主要介绍了浅谈StringBuilder内存碎片对性能的影响,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋...

    .NET骚操作11982022-08-30
  • C#UGUI轮播图组件实现方法详解

    UGUI轮播图组件实现方法详解

    这篇文章主要为大家详细介绍了UGUI轮播图组件的实现方法,支持自动轮播、手势切换等功能,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...

    阿循4102022-07-09
  • C#C# 实现SDL2进行视频播放窗口截图和字幕添加

    C# 实现SDL2进行视频播放窗口截图和字幕添加

    这篇文章主要介绍了C# 实现SDL2进行视频播放窗口截图和字幕添加,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...

    boonya11162022-10-20
  • C#浅谈C#跨线程调用窗体控件(比如TextBox)引发的线程安全问题

    浅谈C#跨线程调用窗体控件(比如TextBox)引发的线程安全问题

    下面小编就为大家分享一篇浅谈C#跨线程调用窗体控件(比如TextBox)引发的线程安全问题,具有很好的参考价值,希望对大家有所帮助...

    绛河7682022-02-10
  • C#C#实现将DataTable内容输出到Excel表格的方法

    C#实现将DataTable内容输出到Excel表格的方法

    这篇文章主要介绍了C#实现将DataTable内容输出到Excel表格的方法,较为详细的分析了C#基于DataTable保存Excel数据的相关技巧,具有一定参考借鉴价值,需要的朋友...

    北风其凉6952021-10-18
  • C#C# 10分钟完成百度人脸识别(入门篇)

    C# 10分钟完成百度人脸识别(入门篇)

    这篇文章主要介绍了C# 10分钟完成百度人脸识别(入门篇),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友...

    学习中的苦与乐4692022-03-09