当多个线程在并发的时候,难免会碰到相互冲突的事情,比如最经典的ATM机的问题,并发不可怕,可怕的是我们没有能力控制。
线程以我的理解可以分为三种
① 锁。
② 互斥。
③ 信号。
好,这一篇主要整理“锁”,C#提供了2种手工控制的锁
一: Monitor类
这个算是实现锁机制的纯正类,在锁定的临界区中只允许让一个线程访问,其他线程排队等待。主要整理为2组方法。
1:Monitor.Enter和Monitor.Exit
微软很照护我们,给了我们语法糖Lock,对的,语言糖确实减少了我们不必要的劳动并且让代码更可观,但是如果我们要精细的
控制,则必须使用原生类,这里要注意一个问题就是“锁住什么”的问题,一般情况下我们锁住的都是静态对象,我们知道静态对象
属于类级别,当有很多线程共同访问的时候,那个静态对象对多个线程来说是一个,不像实例字段会被认为是多个。
不加锁的情况:
1 class Program
2 {
3 static void Main(string[] args)
4 {
5 for (int i = 0; i < 10; i++)
6 {
7 Thread t = new Thread(Run);
8
9 t.Start();
10 }
11 }
12
13 //资源
14 static object obj = new object();
15
16 static int count = 0;
17
18 static void Run()
19 {
20 Thread.Sleep(10);
21
22 Console.WriteLine("当前数字:{0}", ++count);
23 }
24 }
复制代码

加锁的情况:
1 class Program
2 {
3 static void Main(string[] args)
4 {
5 for (int i = 0; i < 10; i++)
6 {
7 Thread t = new Thread(Run);
8
9 t.Start();
10 }
11 }
12
13 //资源
14 static object obj = new object();
15
16 static int count = 0;
17
18 static void Run()
19 {
20 Thread.Sleep(10);
21
22 //进入临界区
23 Monitor.Enter(obj);
24
25 Console.WriteLine("当前数字:{0}", ++count);
26
27 //退出临界区
28 Monitor.Exit(obj);
29 }
30 }
复制代码

2:Monitor.Wait和Monitor.Pulse
首先这两个方法是成对出现,通常使用在Enter,Exit之间。
Wait: 暂时的释放资源锁,然后该线程进入”等待队列“中,那么自然别的线程就能获取到资源锁。
Pulse: 唤醒“等待队列”中的线程,那么当时被Wait的线程就重新获取到了锁。
这里我们是否注意到了两点:
① 可能A线程进入到临界区后,需要B线程做一些初始化操作,然后A线程继续干剩下的事情。
② 用上面的两个方法,我们可以实现线程间的彼此通信。
下面举个例子来模拟两个人的对话。
1 using System;
2 using System.Collections.Generic;
3 using System.Text;
4 using System.Threading;
5
6 namespace Test
7 {
8 public class Program
9 {
10 public static void Main(string[] args)
11 {
12 LockObj obj = new LockObj();
13
14 //注意,这里使用的是同一个资源对象obj
15 Jack jack = new Jack(obj);
16 John john = new John(obj);
17
18 Thread t1 = new Thread(new ThreadStart(jack.Run));
19 Thread t2 = new Thread(new ThreadStart(john.Run));
20
21 t1.Start();
22 t1.Name = "Jack";
23
24 t2.Start();
25 t2.Name = "John";
26
27 Console.ReadLine();
28 }
29 }
30
31 //锁定对象
32 public class LockObj { }
33
34 public class Jack
35 {
36 private LockObj obj;
37
38 public Jack(LockObj obj)
39 {
40 this.obj = obj;
41 }
42
43 public void Run()
44 {
45 Monitor.Enter(this.obj);
46
47 Console.WriteLine("{0}:我已进入茅厕。", Thread.CurrentThread.Name);
48
49 Console.WriteLine("{0}:擦,太臭了,我还是撤!", Thread.CurrentThread.Name);
50
51 //暂时的释放锁资源
52 Monitor.Wait(this.obj);
53
54 Console.WriteLine("{0}:兄弟说的对,我还是进去吧。", Thread.CurrentThread.Name);
55
56 //唤醒等待队列中的线程
57 Monitor.Pulse(this.obj);
58
59 Console.WriteLine("{0}:拉完了,真舒服。", Thread.CurrentThread.Name);
60
61 Monitor.Exit(this.obj);
62 }
63 }
64
65 public class John
66 {
67 private LockObj obj;
68
69 public John(LockObj obj)
70 {
71 this.obj = obj;
72 }
73
74 public void Run()
75 {
76 Monitor.Enter(this.obj);
77
78 Console.WriteLine("{0}:直奔茅厕,兄弟,你还是进来吧,小心憋坏了!",
79 Thread.CurrentThread.Name);
80
81 //唤醒等待队列中的线程
82 Monitor.Pulse(this.obj);
83
84 Console.WriteLine("{0}:哗啦啦....", Thread.CurrentThread.Name);
85
86 //暂时的释放锁资源
87 Monitor.Wait(this.obj);
88
89 Console.WriteLine("{0}:拉完了,真舒服。", Thread.CurrentThread.Name);
90
91 Monitor.Exit(this.obj);
92 }
93 }
94 }
复制代码

二:ReaderWriterLock类
先前也知道,Monitor实现的是在读写两种情况的临界区中只可以让一个线程访问,那么如果业务中存在”读取密集型“操作,就
好比数据库一样,读取的操作永远比写入的操作多。针对这种情况,我们使用Monitor的话很吃亏,不过没关系,ReadWriterLock
就很牛X,因为实现了”写入串行“,”读取并行“。
ReaderWriteLock中主要用3组方法:
<1> AcquireWriterLock: 获取写入锁。
ReleaseWriterLock:释放写入锁。
<2> AcquireReaderLock: 获取读锁。
ReleaseReaderLock:释放读锁。
<3> UpgradeToWriterLock:将读锁转为写锁。
DowngradeFromWriterLock:将写锁还原为读锁。
下面就实现一个写操作,三个读操作,要知道这三个读操作是并发的。
1 namespace Test
2 {
3 class Program
4 {
5 static List<int> list = new List<int>();
6
7 static ReaderWriterLock rw = new System.Threading.ReaderWriterLock();
8
9 static void Main(string[] args)
10 {
11 Thread t1 = new Thread(AutoAddFunc);
12
13 Thread t2 = new Thread(AutoReadFunc);
14
15 t1.Start();
16
17 t2.Start();
18
19 Console.Read();
20 }
21
22 /// <summary>
23 /// 模拟3s插入一次
24 /// </summary>
25 /// <param name="num"></param>
26 public static void AutoAddFunc()
27 {
28 //3000ms插入一次
29 Timer timer1 = new Timer(new TimerCallback(Add), null, 0, 3000);
30 }
31
32 public static void AutoReadFunc()
33 {
34 //1000ms自动读取一次
35 Timer timer1 = new Timer(new TimerCallback(Read), null, 0, 1000);
36 Timer timer2 = new Timer(new TimerCallback(Read), null, 0, 1000);
37 Timer timer3 = new Timer(new TimerCallback(Read), null, 0, 1000);
38 }
39
40 public static void Add(object obj)
41 {
42 var num = new Random().Next(0, 1000);
43
44 //写锁
45 rw.AcquireWriterLock(TimeSpan.FromSeconds(30));
46
47 list.Add(num);
48
49 Console.WriteLine("我是线程{0},我插入的数据是{1}。", Thread.CurrentThread.ManagedThreadId, num);
50
51 //释放锁
52 rw.ReleaseWriterLock();
53 }
54
55 public static void Read(object obj)
56 {
57 //读锁
58 rw.AcquireReaderLock(TimeSpan.FromSeconds(30));
59
60 Console.WriteLine("我是线程{0},我读取的集合为:{1}",
61 Thread.CurrentThread.ManagedThreadId, string.Join(",", list));
62 //释放锁
63 rw.ReleaseReaderLock();
64 }
65 }
66 }
复制代码

分享到:
相关推荐
在“多线程编程之四——线程的同步”这个文件中,可能包含了上述各种同步机制的具体实现示例和详细说明,这对于初学者来说是一份非常宝贵的参考资料。通过学习和理解这些例子,开发者可以更好地掌握如何在实际项目中...
总之,理解并正确使用C++中的信号量机制对于编写高效、可靠的多线程程序至关重要。在MFC工程中,通过自定义信号量类和Windows API,我们可以有效地解决多线程同步问题,确保程序的正确性和性能。
标题与描述均提到了“多线程编程之三——线程间通讯”,这明确指出了文章的核心主题:在多线程编程环境下,不同线程之间的通信机制。在现代软件开发中,尤其是涉及到高性能计算、并发处理以及分布式系统设计时,线程...
1. PowerBuilder 9.0的多线程实现:PB9不直接支持多线程,但可以通过第三方控件或自定义编程实现。 2. Ttimer.ocx控件:这是一个可能用于多线程环境的ActiveX定时器控件,可以触发并发操作。 3. 多线程的优势:多...
* 支持多线程,保证获取到的连接一定是没有被其他线程正在使用 * 按需创建连接,可以创建多个连接,可以控制连接的数量 * 连接被复用,不是每次都重新创建一个新的连接(连接的创建是一个很消耗资源的过程) * ...
本文深入探讨了C#多线程编程中锁机制与线程安全的五个最佳实践,帮助技术人员避开常见陷阱。 一、明确锁的粒度 锁的粒度指的是锁定代码区域的大小。细粒度锁只锁定必要的代码区域,能够减少线程的阻塞时间,提升...
5. **性能测试**:为了评估多线程读写SQLite的性能,通常会进行计时测试。这可以通过`Stopwatch`类来实现,它可以精确地测量代码执行的时间,帮助优化并发策略。 **第一演示项目(firstdemo)**: 这个项目的源代码...
3. **资源竞争**:当多个线程访问相同的资源,如内存、磁盘I/O或数据库连接时,可能会出现竞态条件,需要通过锁或其他同步机制来解决。这会导致线程等待,增加了整体执行时间。 4. **工作负载平衡**:如果40个线程...
在标题中提到的“MFC多线程编程实例——多线程画线源码”,我们主要关注的是如何在同一个窗口或图形界面上,通过多个线程同时执行画线操作。这通常涉及到以下几个关键知识点: 1. **线程基础**:在计算机科学中,...
为了防止数据竞争,可能需要使用锁(如`Monitor`类,`Mutex`,或`Semaphore`)进行线程同步,确保在多线程环境中正确地访问共享资源。 3. **线程优先级**:C#允许为线程设置优先级,如`ThreadPriority.Lowest`、`...
3. **互斥锁(Mutex)**:在多线程环境中,互斥锁用于保护共享资源,确保同一时间只有一个线程可以访问。`std::mutex`类提供了互斥锁的功能,可以防止数据竞争,确保数据的一致性和完整性。在这个demo中,可能使用...
在IT领域,多线程是程序设计中的一个重要概念,它允许程序同时执行多个任务,显著提高了计算机系统的效率和响应速度。C++Builder是一款强大的集成开发环境(IDE),它支持C++语言,为开发者提供了创建多线程应用的...
IEC 62973-5 2023 铁路应用——机车车辆——辅助电源系统用电池——第5部分:锂离子电池.pdf
5. **线程池**:线程池是一种高效的线程管理机制,用于重用已存在的线程而不是每次都创建新的。ThreadPool类提供了一组预先创建的线程,当有新的任务需要执行时,线程池会分配一个空闲线程,从而减少线程创建和销毁...
本主题聚焦于“java线程应用——排序过程动态显示”,通过源码和示例程序来阐述如何利用线程来实时展示排序算法的动态过程。 首先,排序过程动态显示意味着我们将使用线程来逐个步骤地执行排序算法,同时更新用户...
YOLOv5改进系列(15)——增加小目标检测层_yolov5 6.9 增加小目标检测层-CSDN博客.mhtml
C++多线程是现代C++编程中一个重要的特性,它允许程序同时执行多个任务,以提高程序的并发性和效率。在C++11及更高版本中,标准库提供了对多线程的支持,使得开发者可以方便地创建和管理线程。 在C++中,创建线程...
#### 二、Windows多线程编程基础 ##### 1. 线程与进程的关系 - **进程**:是系统进行资源分配和调度的基本单位。 - **线程**:是进程内的一个执行单元,是进程中的最小可调度实体。 在Windows中,一个进程可以...
第一个例子“调试---1.tar.gz”可能是一个用于调试多线程应用程序的工具,帮助开发者跟踪线程间的交互和同步问题。调试多线程程序是一项挑战,因为它涉及到线程间的复杂关系和同步原语,如信号和槽、mutexes、...
5. **线程启动**:在主线程中,创建`CDownloadThread`的实例,调用`CreateThread`或`AfxBeginThread`来启动新线程。 四、线程安全和资源管理 1. **线程安全**:在多线程环境下,访问共享资源(如文件句柄)需要确保...