本笔记摘抄自:https://www.cnblogs.com/zhili/archive/2012/07/21/ThreadsSynchronous.html,记录一下学习过程以备后续查用。

    一、线程同步概述

创建多线程来实现让我们能够更好地响应应用程序,然而当我们创建了多个线程时,就存在多个线程同时访问一个共享资源的情况。此时,我们就需要用到线程同步。线程同

步可以防止数据(共享资源)的损坏。

一般来说,设计应用程序应尽量避免使用线程同步, 因为线程同步会产生一些问题:

1.1、它的使用比较繁琐。我们需要用额外的代码,把多个线程同时访问的数据包围起来,并获取和释放一个线程同步锁。如果有一个代码块忘记获取锁,就有可能造成数据损坏。

1.2、使用线程同步会影响性能。

1.2.1、获取和释放一个锁是需要时间的,我们在决定哪个线程先获取锁的时候,CPU要进行协调,这些额外的工作就会对性能造成影响。

1.2.2、线程同步一次只允许一个线程访问资源,这样就会阻塞线程,而阻塞线程会造成更多的线程被创建。这样CPU就有可能要调度更多的线程,从而对性能造成影响。

    二、线程同步使用

    2.1 使用锁对性能的影响

1.2.1描述过使用锁会对性能产生影响,下面通过比较使用锁和不使用锁消耗的时间来说明这点:

    class Program
{
static void Main(string[] args)
{
#region 线程同步:使用与不使用锁的耗时对比
int x = ;
//迭代500万次
const int iterationNumber = ; //不使用锁
Stopwatch sw = Stopwatch.StartNew();
for (int i = ; i < iterationNumber; i++)
{
x++;
}
Console.WriteLine("Total time consuming is:{0}ms.", sw.ElapsedMilliseconds); sw.Restart();
//使用锁
for (int i = ; i < iterationNumber; i++)
{
Interlocked.Increment(ref x);
} Console.WriteLine("Total time consuming is:{0}ms.", sw.ElapsedMilliseconds);
Console.Read();
#endregion
}
}

运行结果如下:

    2.2 Interlocked实现线程同步

Interlocked为多个线程共享变量提供了原子操作,当我们在多线程中对一个整数进行递增操作时,就需要实现线程同步。

下面代码演示加锁与不加锁的区别:

不加锁:

    class Program
{
//共享资源
public static int number = ; static void Main(string[] args)
{
#region 线程同步:使用Interlocked实现线程同步
//不加锁
for (int i = ; i < ; i++)
{
Thread thread = new Thread(Add);
thread.Start();
}
Console.Read();
#endregion
} /// <summary>
/// 递增不加锁
/// </summary>
public static void Add()
{
Thread.Sleep();
Console.WriteLine("The current value of number is:{0}", ++number);
}
}

运行结果如下:

结果与预期可能不太一样。为了解决这样的问题,我们可以通过使用 Interlocked.Increment方法来实现自增操作。

实现原理:类似银行叫号,当有空号且号码是自己的,才能去办理相关的业务,否则继续等待。

加锁:

    class Program
{
//共享资源
public static int number = ;
public static long signal = ; static void Main(string[] args)
{
#region 线程同步:使用Interlocked实现线程同步
//加锁
for (int i = ; i < ; i++)
{
Thread thread = new Thread(new ParameterizedThreadStart(AddWithInterlocked));
thread.Start(i);
}
Console.Read();
#endregion
} /// <summary>
/// 递增加Interlocked锁
/// </summary>
public static void AddWithInterlocked(object parameter)
{
while (Interlocked.Read(ref signal) != || (int)parameter != number)
{
Thread.Sleep();
} Interlocked.Increment(ref signal);
Console.WriteLine("The current value of number is:{0}", ++number);
Interlocked.Decrement(ref signal);
}
}

运行结果如下:

2.3 Monitor实现线程同步

对于上面那个情况,也可以通过Monitor.Enter和Monitor.Exit方法来实现线程同步。

C#中通过lock关键字来提供简化的语法(lock可以理解为Monitor.Enter和Monitor.Exit方法的语法糖)。

    class Program
{
//共享资源
public static int number = ;
private static readonly object addLock = new object(); static void Main(string[] args)
{
#region 线程同步:使用Monitor实现线程同步
//非语法糖
for (int i = ; i < ; i++)
{
Thread thread = new Thread(AddWithMonitor);
thread.Start();
}
Console.Read();
//语法糖
//for (int i = 0; i < 10; i++)
//{
// Thread thread = new Thread(AddWithLock);
// thread.Start();
//}
//Console.Read();
#endregion
} /// <summary>
/// 递增加Monitor锁
/// </summary>
public static void AddWithMonitor()
{
Thread.Sleep();
Monitor.Enter(addLock);
Console.WriteLine("The current value of number is:{0}", ++number);
Monitor.Exit(addLock);
} /// <summary>
/// 递增加Lock锁
/// </summary>
public static void AddWithLock()
{
Thread.Sleep();
lock (addLock)
{
Console.WriteLine("The current value of number is:{0}", ++number);
}
}
}

运行结果如下:

接上面的addLock锁(以下描述为obj锁),顺便学习一下Monitor类的原理:

Monitor在锁对象obj上会维持两个线程队列R和W以及一个引用T :

(1)T是对当前获得了obj锁的线程的引用

(2) R为就绪队列。

 R队列上的线程,是已经准备好了去竞争获取obj锁的线程。

线程可通过调用Monitor.Enter(obj)或Monitor.TryEnter(obj)而直接进入R队列,可通过调用Monitor.Exit(obj)或Monitor.Wait(obj)释放其所获得的obj锁。

当obj锁被某个线程释放后,这个队列上的线程就会去竞争obj锁,而获得obj锁的线程将被T引用。

(3) W为等待队列。

    W队列上的线程,是不会被OS直接调度执行的线程。也就是说,等待队列上的线程不能去获得obj锁。

线程可通过调用Monitor.Wait(obj)而直接进入W队列,可通过调用Monitor.Pulse(obj)或Monitor.PulseAll(obj)将W队列中的第一个等待线程或所有等待线程移至R队列

这时被移至R队列的这些线程就有机会被OS直接调度执行,也就是可以去竞争obj锁。

(4)Monitor的成员方法。

    Monitor.Enter(obj)/Monitor.TryEnter(obj) :线程会进入R队列以等待获取obj锁

    Monitor.Exit(obj) :线程释放obj锁(只有获取了obj锁的线程才能执行Monitor.Exit(obj))

    Monitor.Wait(obj): 线程释放当前获得的obj锁,然后进入W队列并阻塞。

    Monitor.Pulse(obj) :将W队列中的第一个等待线程移至R队列中以使第一个线程有机会获取obj锁。

    Monitor.PulseAll(obj):将W队列中的所有等待线程移至R队列以使得这些线程有机会获得obj锁。

下面代码演示Monitor.Wait及Monitor.Pulse的使用:

    class Program
{
//共享资源
private static readonly object addLock = new object(); static void Main(string[] args)
{
#region 线程同步:Monitor.Wait与Monitor.Pulse的使用
for (int i = ; i < ; i++)
{
Thread thread = new Thread(MonitorWaitAndPulse);
thread.Start();
}
Console.Read();
#endregion
} /// <summary>
/// Monitor中的Wait与Pulse方法
/// </summary>
public static void MonitorWaitAndPulse()
{
//进入就绪队列等待获取锁资源
Monitor.Enter(addLock);
//进来打声招呼
Console.WriteLine("{0}:我来了,临时要出去办一下事。", Thread.CurrentThread.ManagedThreadId);
//唤醒等待队列中的第一个线程进入就绪队列
Monitor.Pulse(addLock);
//暂时释放锁资源进入等待队列
Monitor.Wait(addLock);
//出去办事
Thread.Sleep();
//回来打声招呼
Console.WriteLine("{0}:我回来了。", Thread.CurrentThread.ManagedThreadId);
//释放锁资源
Monitor.Exit(addLock);
}
}

运行结果如下:

    2.4 ReaderWriterLock实现线程同步

如果我们需要对一个共享资源执行多次读取时,用前面所讲的类实现的同步锁都仅允许一个线程进行访问,而其它线程将被阻塞。由于只是进行读取操作,其实是没有必要

堵塞其他的线程, 应该让它们并发的执行。

此时,可通过ReaderWriterLock类来实现并行读取。

    class Program
{
//创建对象
public static List<int> lists = new List<int>();
public static ReaderWriterLock readerWriteLock = new ReaderWriterLock(); static void Main(string[] args)
{
#region 线程同步:使用ReaderWriterLock实现线程同步
//创建一个线程读取数据
Thread threadWrite = new Thread(Write);
threadWrite.Start();
//创建10个线程读取数据
for (int i = ; i < ; i++)
{
Thread threadRead = new Thread(Read);
threadRead.Start();
} Console.Read();
#endregion
} /// <summary>
/// 写入方法
/// </summary>
public static void Write()
{
//获取写入锁,以10毫秒为超时。
readerWriteLock.AcquireWriterLock();
Random ran = new Random();
int count = ran.Next(, );
lists.Add(count);
Console.WriteLine("Write the data is:" + count);
//释放写入锁
readerWriteLock.ReleaseWriterLock();
} /// <summary>
/// 读取方法
/// </summary>
public static void Read()
{
Thread.Sleep();
//获取读取锁
readerWriteLock.AcquireReaderLock(); foreach (int list in lists)
{
//输出读取的数据
Console.WriteLine(list);
} // 释放读取锁
readerWriteLock.ReleaseReaderLock();
}
}

运行结果如下:

C#线程学习笔记四:线程同步的更多相关文章

  1. JUC源码学习笔记5——线程池,FutureTask,Executor框架源码解析

    JUC源码学习笔记5--线程池,FutureTask,Executor框架源码解析 源码基于JDK8 参考了美团技术博客 https://tech.meituan.com/2020/04/02/jav ...

  2. 操作系统学习笔记----进程/线程模型----Coursera课程笔记

    操作系统学习笔记----进程/线程模型----Coursera课程笔记 进程/线程模型 0. 概述 0.1 进程模型 多道程序设计 进程的概念.进程控制块 进程状态及转换.进程队列 进程控制----进 ...

  3. C#线程学习笔记九:async & await入门二

    一.异步方法返回类型 只能返回3种类型(void.Task和Task<T>). 1.1.void返回类型:调用方法执行异步方法,但又不需要做进一步的交互. class Program { ...

  4. java学习笔记15--多线程编程基础2

    本文地址:http://www.cnblogs.com/archimedes/p/java-study-note15.html,转载请注明源地址. 线程的生命周期 1.线程的生命周期 线程从产生到消亡 ...

  5. Linux进程线程学习笔记:运行新程序

    Linux进程线程学习笔记:运行新程序                                         周银辉 在上一篇中我们说到,当启动一个新进程以后,新进程会复制父进程的大部份上下 ...

  6. muduo网络库学习笔记(四) 通过eventfd实现的事件通知机制

    目录 muduo网络库学习笔记(四) 通过eventfd实现的事件通知机制 eventfd的使用 eventfd系统函数 使用示例 EventLoop对eventfd的封装 工作时序 runInLoo ...

  7. {Python之线程} 一 背景知识 二 线程与进程的关系 三 线程的特点 四 线程的实际应用场景 五 内存中的线程 六 用户级线程和内核级线程(了解) 七 python与线程 八 Threading模块 九 锁 十 信号量 十一 事件Event 十二 条件Condition(了解) 十三 定时器

    Python之线程 线程 本节目录 一 背景知识 二 线程与进程的关系 三 线程的特点 四 线程的实际应用场景 五 内存中的线程 六 用户级线程和内核级线程(了解) 七 python与线程 八 Thr ...

  8. ZooKeeper学习笔记四:使用ZooKeeper实现一个简单的分布式锁

    作者:Grey 原文地址: ZooKeeper学习笔记四:使用ZooKeeper实现一个简单的分布式锁 前置知识 完成ZooKeeper集群搭建以及熟悉ZooKeeperAPI基本使用 需求 当多个进 ...

  9. Java IO学习笔记四:Socket基础

    作者:Grey 原文地址:Java IO学习笔记四:Socket基础 准备两个Linux实例(安装好jdk1.8),我准备的两个实例的ip地址分别为: io1实例:192.168.205.138 io ...

随机推荐

  1. nvidia gtx1050在kali linux系统下安装显卡驱动,且可以使用x-setting切换显卡

    转自:https://www.zzhsec.com/255.html 1.更换源[使用中科大或者官方源都可以] 下面使用中科大的源 root@Andy:/home/dnt# vi /etc/apt/s ...

  2. Java 9 ← 2017,2019 Java → 13 ,都发生了什么?

    距离 2019 年结束,只剩下 35 天了.你做好准备迎接 2020 年了吗? 一到年底,人就特别容易陷入回忆和比较之中,比如说这几天, 的对比挑战就火了! 这个话题登上了微博的热搜榜,也刷爆了朋友圈 ...

  3. 新闻实时分析系统 Spark2.X环境准备、编译部署及运行

    1.Spark概述 Spark 是一个用来实现快速而通用的集群计算的平台. 在速度方面, Spark 扩展了广泛使用的 MapReduce 计算模型,而且高效地支持更多计算模式,包括交互式查询和流处理 ...

  4. sku二维数组里的数组从头到尾叠加组合

    今天工作之余与同事聊天,要是实现一个sku描述里的字段组合的问题.并且实现了请吃饭.哈哈.一顿饭,我和另一位同事积极杠杆的.后来实现了出来. let skuList = [ ['黑色', '白色',' ...

  5. iptables简单命令

    IPTables是基于Netfilter基本架构实现的一个可扩展的数据报高级管理系统或核外配置工具,利用table.chain.rule三级来存储数据报的各种规则.Netfilter-iptables ...

  6. [学习笔记] [数据分析] 01.Python入门

    1.安装Python与环境配置 ① ② 安装pip以及利用pip安装Python库 2.Anaconda安装 conda list 要在root环境下 3.常用数据分析库 ① Numpy 安装:con ...

  7. JS 输出指定范围内的随机数

    /* 自定义函数 */ function GetRandomNum(Min,Max){ var Range = Max - Min; var Rand = Math.random(); return( ...

  8. 链式栈-C语言实现

    相对于顺序栈的空间有限,链式栈的操作则更加灵活 #include<stdio.h> #include<malloc.h> typedef int SElemType; //元素 ...

  9. javascript基础修炼(13)——记一道有趣的JS脑洞练习题【华为云技术分享】

    版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/devcloud/article/detai ...

  10. 学习索引结构的一些案例——Jeff Dean在SystemML会议上发布的论文(下)

    [摘要] 除了范围索引之外,点查找的Hash Map在DBMS中起着类似或更重要的作用. 从概念上讲,Hash Map使用Hash函数来确定性地将键映射到数组内的随机位置(参见图[9 ],只有4位开销 ...