目录:

一、线程同步概述

二、线程同步的使用

三 、总结

一、线程同步概述

前面的文章都是讲创建多线程来实现让我们能够更好的响应应用程序,然而当我们创建了多个线程时,就存在多个线程同时访问一个共享的资源的情况,在这种情况下,就需要我们用到线程同步,线程同步可以防止数据(共享资源)的损坏。

然而我们在设计应用程序还是要尽量避免使用线程同步, 因为线程同步会产生一些问题:

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

2. 使用线程同步会影响性能,获取和释放一个锁肯定是需要时间的吧,因为我们在决定哪个线程先获取锁时候, CPU必须进行协调,进行这些额外的工作就会对性能造成影响

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

所以在实际的设计中还是要尽量避免使用线程同步,因此我们要避免使用一些共享数据,例如静态字段。

二、线程同步的使用

2.1 对于使用锁性能的影响

上面已经说过使用锁将会对性能产生影响, 下面通过比较使用锁和不使用锁时消耗的时间来说明这点

using System;
using System.Diagnostics;
using System.Threading; namespace InterlockedSample
{
// 比较使用锁和不使用锁锁消耗的时间
// 通过时间来说明使用锁性能的影响
class Program
{
static void Main(string[] args)
{
int x = 0;
// 迭代次数为500万
const int iterationNumber = 5000000;
// 不采用锁的情况
// StartNew方法 对新的 Stopwatch 实例进行初始化,将运行时间属性设置为零,然后开始测量运行时间。
Stopwatch sw = Stopwatch.StartNew();
for (int i = 0; i < iterationNumber; i++)
{
x++;
} Console.WriteLine("Use the all time is :{0} ms", sw.ElapsedMilliseconds); sw.Restart();
// 使用锁的情况
for (int i = 0; i < iterationNumber; i++)
{
Interlocked.Increment(ref x);
} Console.WriteLine("Use the all time is :{0} ms", sw.ElapsedMilliseconds);
Console.Read();
}
}
}

运行结果(这是在我电脑上运行的结果)从结果中可以看出加了锁的运行速度慢了好多(慢了11倍 197/18 ):

2.2 Interlocked实现线程同步

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

因为增加变量操作(++运算符)不是一个原子操作,需要执行下列步骤:

1)将实例变量中的值加载到寄存器中。

2)增加或减少该值。

3)在实例变量中存储该值。

如果不使用 Interlocked.Increment方法,线程可能会在执行完前两个步骤后被抢先。然后由另一个线程执行所有三个步骤,此时第一个线程还没有把变量的值存储到实例变量中去,而另一个线程就可以把实例变量加载到寄存器里面读取了(此时加载的值并没有改变),所以会导致出现的结果不是我们预期的,相信这样的解释可以帮助大家更好的理解Interlocked.Increment方法和 原子性操作,

下面通过一段代码来演示下加锁和不加锁的区别(开始讲过加锁会对性能产生影响,这里将介绍加锁来解决线程同步的问题,得到我们预期的结果):

不加锁的情况:

class Program
{
static void Main(string[] args)
{ for (int i = 0; i < 10; i++)
{
Thread testthread = new Thread(Add);
testthread.Start();
} Console.Read();
} // 共享资源
public static int number = 1; public static void Add()
{
Thread.Sleep(1000);
Console.WriteLine("the current value of number is:{0}", ++number);
}
}

运行结果(不同电脑上可能运行的结果和我的不一样,但是都是得到不是预期的结果的):

为了解决这样的问题,我们可以通过使用 Interlocked.Increment方法来实现原子的自增操作。

代码很简单,只需要把++number改成Interlocked.Increment(ref number)就可以得到我们预期的结果了,在这里代码和运行结果就不贴了。

总之Interlocked类中的方法都是执行一次原子读取以及写入的操作的。

2.3 Monitor实现线程同步

对于上面那个情况也可以通过Monitor.Enter和Monitor.Exit方法来实现线程同步。C#中通过lock关键字来提供简化的语法(lock可以理解为Monitor.Enter和Monitor.Exit方法的语法糖),代码也很简单:

using System;
using System.Threading; namespace MonitorSample
{
class Program
{
static void Main(string[] args)
{
for (int i = 0; i < 10; i++)
{
Thread testthread = new Thread(Add);
testthread.Start();
} Console.Read();
} // 共享资源
public static int number = 1; public static void Add()
{
Thread.Sleep(1000);
//获得排他锁
Monitor.Enter(number); Console.WriteLine("the current value of number is:{0}", number++); // 释放指定对象上的排他锁。
Monitor.Exit(number);
}
}
}

运行结果当然是我们所期望的:

在 Monitor类中还有其他几个方法在这里也介绍,只是让大家引起注意下,一个Wait方法,很明显Wait方法的作用是:释放某个对象上的锁以便允许其他线程锁定和访问这个对象。第二个就是TryEnter方法,这个方法与Enter方法主要的区别在于是否阻塞当前线程,当一个对象通过Enter方法获取锁,而没有执行Exit方法释放锁,当另一个线程想通过Enter获得锁时,此时该线程将会阻塞,直到另一个线程释放锁为止,而TryEnter不会阻塞线程。具体代码就不不写出来了。

2.4 ReaderWriterLock实现线程同步

如果我们需要对一个共享资源执行多次读取时,然而用前面所讲的类实现的同步锁都只允许一个线程允许,所有线程将阻塞,但是这种情况下肯本没必要堵塞其他线程, 应该让它们并发的执行,因为我们此时只是进行读取操作,此时通过ReaderWriterLock类可以很好的实现读取并行。

演示代码为:

using System;
using System.Collections.Generic;
using System.Threading; namespace ReaderWriterLockSample
{
class Program
{
public static List<int> lists = new List<int>(); // 创建一个对象
public static ReaderWriterLock readerwritelock = new ReaderWriterLock();
static void Main(string[] args)
{
//创建一个线程读取数据
Thread t1 = new Thread(Write);
t1.Start();
// 创建10个线程读取数据
for (int i = 0; i < 10; i++)
{
Thread t = new Thread(Read);
t.Start();
} Console.Read(); } // 写入方法
public static void Write()
{
// 获取写入锁,以10毫秒为超时。
readerwritelock.AcquireWriterLock(10);
Random ran = new Random();
int count = ran.Next(1, 10);
lists.Add(count);
Console.WriteLine("Write the data is:" + count);
// 释放写入锁
readerwritelock.ReleaseWriterLock();
} // 读取方法
public static void Read()
{
// 获取读取锁
readerwritelock.AcquireReaderLock(10); foreach (int li in lists)
{
// 输出读取的数据
Console.WriteLine(li);
} // 释放读取锁
readerwritelock.ReleaseReaderLock();
}
}
}

运行结果:

三、总结

本文中主要介绍如何实现多线程同步的问题, 通过线程同步可以防止共享数据的损坏,但是由于获取锁的过程会有性能损失,所以在设计应用过程中尽量减少线程同步的使用。本来还要介绍互斥(Mutex), 信号量(Semaphore), 事件构造的, 由于篇幅的原因怕影响大家的阅读,所以这剩下的内容放在后面介绍的。

http://www.cnblogs.com/zhili/archive/2012/07/21/ThreadsSynchronous.html

[C# 线程处理系列]专题四:线程同步的更多相关文章

  1. 线程池系列一:线程池作用及Executors方法讲解

    线程池的作用: 线程池作用就是限制系统中执行线程的数量.     根据系统的环境情况,可以自动或手动设置线程数量,达到运行的最佳效果:少了浪费了系统资源,多了造成系统拥挤效率不高.用线程池控制线程数量 ...

  2. [C# 基础知识系列]专题四:事件揭秘

    转自http://www.cnblogs.com/zhili/archive/2012/10/27/Event.html 引言: 前面几个专题对委托进行了详细的介绍的,然后我们在编写代码过程中经常会听 ...

  3. [C# 基础知识系列]专题四:事件揭秘 (转载)

    引言: 前面几个专题对委托进行了详细的介绍的,然后我们在编写代码过程中经常会听到“事件”这个概念的,尤其是写UI的时候,当我们点击一个按钮后VS就会自动帮我们生成一些后台的代码,然后我们就只需要在Cl ...

  4. [.NET领域驱动设计实战系列]专题四:前期准备之工作单元模式(Unit Of Work)

    一.前言 在前一专题中介绍了规约模式的实现,然后在仓储实现中,经常会涉及工作单元模式的实现.然而,在我的网上书店案例中也将引入工作单元模式,所以本专题将详细介绍下该模式,为后面案例的实现做一个铺垫. ...

  5. [C# 网络编程系列]专题四:自定义Web浏览器

    转自:http://www.cnblogs.com/zhili/archive/2012/08/24/WebBrowser.html 前言: 前一个专题介绍了自定义的Web服务器,然而向Web服务器发 ...

  6. [.NET领域驱动设计实战系列]专题十一:.NET 领域驱动设计实战系列总结

    一.引用 其实在去年本人已经看过很多关于领域驱动设计的书籍了,包括Microsoft .NET企业级应用框架设计.领域驱动设计C# 2008实现.领域驱动设计:软件核心复杂性应对之道.实现领域驱动设计 ...

  7. [ 高并发]Java高并发编程系列第二篇--线程同步

    高并发,听起来高大上的一个词汇,在身处于互联网潮的社会大趋势下,高并发赋予了更多的传奇色彩.首先,我们可以看到很多招聘中,会提到有高并发项目者优先.高并发,意味着,你的前雇主,有很大的业务层面的需求, ...

  8. Java Thread系列(四)线程通信

    Java Thread系列(四)线程通信 一.传统通信 public static void main(String[] args) { //volatile实现两个线程间数据可见性 private ...

  9. Android异步处理系列文章四篇之一使用Thread+Handler实现非UI线程更新UI界面

    目录: Android异步处理一:使用Thread+Handler实现非UI线程更新UI界面Android异步处理二:使用AsyncTask异步更新UI界面Android异步处理三:Handler+L ...

随机推荐

  1. 从XML文件乱码问题,探寻其背后的原理 (ZHUAN)

    出现应用程序读取XML文件乱码的场景: 加入xml文件以<?xml version="1.0" encoding="utf-8" ?> 格式的:如果 ...

  2. 开启MySQL的sql语句记录

    在开发的时候经常会想看一下MySQL最终执行的sql或者想保存sql记录,所以我们可以启用MySQL的sql记录功能. 开启方法:Linux下编辑MySQL的my.cnf文件,windows下编辑my ...

  3. CentOS7自动补齐

    cenos7,最小安装,做服务器嘛.但是发现tab键的自动补齐功能没有:其实可以直接把centos7作为yum源,然后直接安装bash-completion  yum install -y bash- ...

  4. Oracle查询数据表结构(字段,类型,大小,备注)

    作用:想要生成整个Oracle数据库所有表结构WORD文档(数据库设计说明书) Oracle数据库字典介绍    Oracle数据字典是有表和视图组成的,存储有关数据库结构信息的一些数据库对象.数据库 ...

  5. Jet Brains家族XX方法

    声明:本文转载自 https://www.jianshu.com/p/f404994e2843 2018/11/23

  6. MySQL 预处理语句prepare、execute、deallocate的使用

    所以对于中文乱码,需要去check的地方有如下3个:1.mysql窗口的字符编码(xshell连接的远程工具的字符集设置):2.数据库的字符编码(show variables like '%char% ...

  7. 使用Jersey和Jetty开发RESTful Web service

    Jersey RESTful 框架是开源的RESTful框架, 实现了JAX-RS (JSR 311 & JSR 339) 规范,是JAX-RS的参考实现,并且提供了更多的特性和工具,简化了R ...

  8. boost的asio接收单路大数据量udp包的方法

    开发windows客户端接收RTP视频流,当h264视频达到1080P 60fps的时候,按包来调用recvfrom的函数压力比较大,存在丢包的问题,windows的完成端口的性能效果当然可以解决这个 ...

  9. JavaWeb开发Eclipse环境配置--史上最详细的教程

    [前言] JSP本身是JavaWeb中的知识,但是在学习Android网络时,必然要涉及到与服务器之间的交互,所以学一下JSP以及其他JavaWeb的内容还是很有必要的,至少能明白程序在访问服务器时, ...

  10. maven学习7 settings.xml解析

    maven的配置文件settings.xml存在于两个地方: 1.安装的地方:${M2_HOME}/conf/settings.xml 2.用户的目录:${user.home}/.m2/setting ...