C# 应用 - 多线程 7) 处理同步数据之 Synchronized code regions (同步代码区域): Monitor 和 lock
目录:
- System.Threading.Monitor:提供同步访问对象的机制;
- lock 是语法糖,是对 Monitor Enter 和 Exit 方法的一个封装
- lock 案例
1. Monitor
1. 基本方法
- public static void Enter(object obj);
在指定对象上获取排他锁。 - public static void Exit(object obj);
释放指定对象上的排他锁。
2. 使用例子
// 被 Monitor 保护的队列
private Queue<T> m_inputQueue = new Queue<T>();
// 给 m_inputQueue 加锁,并往 m_inputQueue 添加一个元素
public void Enqueue(T qValue)
{
// 请求获取锁,并阻塞其他线程获得该锁,直到获得锁
Monitor.Enter(m_inputQueue);
try
{
m_inputQueue.Enqueue(qValue);
}
finally
{
// 释放锁
Monitor.Exit(m_inputQueue);
}
}
2. lock
lock 是语法糖,是对Monitor的Enter和Exit的一个封装。
lock (m_inputQueue) {} 等价于
bool __lockWasTaken = false;
try
{
System.Threading.Monitor.Enter(m_inputQueue, ref __lockWasTaken);
}
finally
{
if (__lockWasTaken) System.Threading.Monitor.Exit(m_inputQueue);
}
- 当同步对共享资源的线程访问时,请锁定专用对象实例(例如,private readonly object balanceLock = new object();)或另一个不太可能被代码无关部分用作 lock 对象的实例。 避免对不同的共享资源使用相同的 lock 对象实例,因为这可能导致死锁或锁争用;
- 具体而言,避免将以下对象用作 lock 对象:
1)this(调用方可能将其用作 lock)
2)Type 实例(可以通过 typeof 运算符或反射获取)
3)字符串实例,包括字符串文本,(这些可能是暂存的)。
尽可能缩短持有锁的时间,以减少锁争用。
private readonly object balanceLock = new object();
private Queue<T> m_inputQueue = new Queue<T>();
public void Enqueue(T qValue)
{
lock (balanceLock)
{
m_inputQueue.Enqueue(qValue);
}
}
3. lock 案例
1. 数据库访问工厂单例模式
private static object _iBlockPortLockObj = new object();
private static IBlockPort _iBlockPort;
/// <summary>
/// 卡口
/// </summary>
/// <returns></returns>
public static IBlockPort CreateBlockPort()
{
if (_iBlockPort == null)
{
lock (_iBlockPortLockObj)
{
if (_iBlockPort == null)
{
string className = AssemblyName + "." + db + "BlockPort";
_iBlockPort = (IBlockPort)Assembly.Load(AssemblyName).CreateInstance(className);
}
}
}
return _iBlockPort;
}
2. 队列进出
public abstract class AbstractCache<T> where T : ICloneable
{
protected int queenLength = 30; // 保持队列的最大长度,主要可能考虑内存
/// <summary>
/// 过车缓存列表
/// </summary>
public List<T> listCache { get; set; }
protected object _lockObj = new object();
/// <summary>
/// 初始化或重置缓存列表
/// </summary>
protected void RefreshListCache()
{
lock (_lockObj)
{
if (listCache == null)
{
listCache = new List<T>();
}
else
{
listCache.Clear();
}
}
}
/// <summary>
/// 添加新的数据进队列,后续考虑做成环形队列减少开销
/// </summary>
/// <param name="list"></param>
protected void AddListToCache(List<T> list)
{
lock (_lockObj)
{
if (listCache == null) return;
listCache.InsertRange(0, list);
if (listCache.Count > queenLength)
{
listCache.RemoveRange(queenLength, listCache.Count - queenLength);
}
}
}
/// <summary>
/// 移除并返回过车缓存队列的最后一个元素
/// </summary>
/// <returns></returns>
public T DequeueLastCar()
{
T res = default;
lock (_lockObj)
{
if (listCache != null && listCache.Count > 0)
{
int lastIndex = listCache.Count - 1;
res = (T)listCache[lastIndex].Clone();
listCache.RemoveAt(lastIndex);
}
}
return res;
}
}
- 前提:在某项目上,view 的控件包括一个下拉框(可选idA、idB等)、一个图片 image;
- 数据逻辑设计:线程 A 定时根据下拉框的选择作为条件从第三方的数据库获取数据并添加进队列
1)线程 B 定时从队列取出一个并展示到 image 控件
2)当下拉框切换选择时,清空队列 [便于展示跟下拉框关联的图片] - 问题:从第三方的数据库取数据需要 1s 左右,如果刚好出现这样的操作:线程 A 查数据库获取 idA 相关的数据(将持续 1s)-> 下拉框 idA 切换到 idB 并触发执行清空队列操作 -> 线程 A 将 idA 的数据添加到队列,将会出现下拉框切换 idB 之后依旧展示 idA 相关的数据。
- 解决:在线程 a 查数据库时就对队列加锁(同时去掉队列入队的锁,避免死锁),这样在获取数据的中途切换下拉框,就能等到获取完并加入队列后再清空。
- 导致新的问题:在获取的过程中,因队列被锁,导致无法线程 B 出队的操作被阻塞。
- 解决:入队和出队共用一个锁,从数据库获取数据和清空队列共用一个锁。
/// <summary>
/// 添加新的数据进队列,后续考虑做成环形队列减少开销
/// 清空、添加、取出一个数据,都需要加锁,但是由于添加的数据是从海康那边拿过来的,可能需要几秒的时间,
/// 可能会导致这样的结果:线程 A 查数据库(持续几秒)-> 线程 B 执行清空队列操作 -> 线程 A 将数据添加到队列
/// 因此将,锁直接移动到 lock {线程 A 查数据库、将数据添加到队列}
/// </summary>
/// <param name="list"></param>
protected void AddListToCache(List<T> list)
{
if (listCache == null) return;
listCache.InsertRange(0, list);
if (listCache.Count > queenLength)
{
listCache.RemoveRange(queenLength, listCache.Count - queenLength);
}
}
CancellationTokenSource source = new CancellationTokenSource();
/// <summary>
/// 定时获取 xx 数据
/// </summary>
public void GetPassCarInterval()
{
Task.Factory.StartNew(() =>
{
while (!source.IsCancellationRequested)
{
if (!string.IsNullOrWhiteSpace(xx))
{
lock (_lockObj)
{
// 从数据库获取数据
var list = GetPassCarInfo.GetLastBlockPortCarRecordBy(xx);
AddListToCache(list);
}
}
AutoReset.WaitOne(Common.GetDataTimespan);
}
}, TaskCreationOptions.LongRunning);
}
C# 应用 - 多线程 7) 处理同步数据之 Synchronized code regions (同步代码区域): Monitor 和 lock的更多相关文章
- 虎牙在全球 DNS 秒级生效上的实践 集群内通过 raft 协议同步数据,毫秒级别完成同步。
https://mp.weixin.qq.com/s/9bEiE4QFBpukAfNOYhmusw 虎牙在全球 DNS 秒级生效上的实践 原创: 周健&李志鹏 阿里巴巴中间件 今天
- Java同步块(synchronized block)使用详解
Java 同步块(synchronized block)用来标记方法或者代码块是同步的.Java同步块用来避免竞争.本文介绍以下内容: Java同步关键字(synchronzied) 实例方法同步 静 ...
- Java多线程学习---------超详细总结(java 多线程 同步 数据传递 )
目录(?)[-] 一扩展javalangThread类 二实现javalangRunnable接口 三Thread和Runnable的区别 四线程状态转换 五线程调度 六常用函数说明 使用方式 为什么 ...
- Java多线程:线程同步与关键字synchronized
一.同步的特性1. 不必同步类中所有的方法, 类可以同时拥有同步和非同步方法.2. 如果线程拥有同步和非同步方法, 则非同步方法可以被多个线程自由访问而不受锁的限制. 参见实验1:http://blo ...
- Java 多线程 死锁 隐性死锁 数据竞争 恶性数据竞争 错误解决深入分析 全方向举例
在几乎所有编程语言中,由于多线程引发的错误都有着难以再现的特点,程序的死锁或其它多线程错误可能只在某些特殊的情形下才出现,或在不同的VM上运行同一个程序时错误表现不同.因此,在编写多线程程序时,事先认 ...
- java 多线程总结篇3之——生命周期和线程同步
一.生命周期 线程的生命周期全在一张图中,理解此图是基本: 线程状态图 一.新建和就绪状态 当程序使用new关键字创建了一个线程之后,该线程就处于新建状态,此时它和其他的Java对象一样,仅仅由Jav ...
- Java多线程面试题:线程锁+线程池+线程同步等
1.并发编程三要素? 1)原子性 原子性指的是一个或者多个操作,要么全部执行并且在执行的过程中不被其他操作打断,要么就全部都不执行. 2)可见性 可见性指多个线程操作一个共享变量时,其中一个线程对变量 ...
- centos6.5下部署sersync+rsync --daemon同步数据
rsync --daemon端配置 [root@rsync-daemon etc]# /etc/init.d/iptables stop [root@rsync-daemon ~]# dos2unix ...
- 巧用 JuiceFS Sync 命令跨云迁移和同步数据
近年来,云计算已成为主流,企业从自身利益出发,或是不愿意被单一云服务商锁定,或是业务和数据冗余,或是出于成本优化考虑,会尝试将部分或者全部业务从线下机房迁移到云或者从一个云平台迁移到另一个云平台,业务 ...
随机推荐
- ElasticSearch 交互使用
Curl 命令 # 建立索引 [root@dbtest01 ~]# curl -XPUT 'http://10.0.0.121:9200/test' # 插入数据 [root@dbtest01 ~]# ...
- Kubernets二进制安装(14)之flannel之SNAT规则优化
flannel之SNAT规则优化的目的是由于在K8S中的容器内,访问不同宿主机中的容器的资源的时候,日志文件会记录为宿主机的IP地址,而不是记录为容器本身自己的IP地址,建议在不同的宿主机上的容器互访 ...
- Leetcode(18)-四数之和
给定一个包含 n 个整数的数组 nums 和一个目标值 target,判断 nums 中是否存在四个元素 a,b,c 和 d ,使得 a + b + c + d 的值与 target 相等?找出所有满 ...
- HDU 5608 function(莫比乌斯反演 + 杜教筛)题解
题意: 已知\(N^2-3N+2=\sum_{d|N}f(d)\),求\(\sum_{i=1}^nf(i) \mod 1e9+7\),\(n\leq1e9\) 思路: 杜教筛基础题? 很显然这里已经设 ...
- USB2.0协议学习笔记---基本概念
概念 USB是一种串行通信总线(Universal Serial Bus),经历的版本有USB1.0,USB1.1.USB2.0等.USB是一种主从模式的结构,因此它无法在设备与设备.主机与主机之间 ...
- 十大排序算法时间复杂度 All In One
十大排序算法时间复杂度 All In One 排序算法时间复杂度 排序算法对比 Big O O(n) O(n*log(n)) O(n^2) 冒泡排序 选择排序 插入排序 快速排序 归并排序 基数排序 ...
- flat array
flat array 已知如下数组: var arr = [ [1, 2, 2], [3, 4, 5, 5], [6, 7, 8, 9, [11, 12, [12, 13, [14] ] ] ], 1 ...
- SVG & Blob & Base64
SVG & Blob https://developer.mozilla.org/en-US/docs/Web/API/Blob SVG & Base64 https://develo ...
- css grid layout in practice
css grid layout in practice https://caniuse.com/#search=grid subgrid https://caniuse.com/#search=cal ...
- c++ 动态解析PE导出表
测试环境是x86 main #include <iostream> #include <Windows.h> #include <TlHelp32.h> #incl ...