目录:

一、线程同步概述

二、线程同步的使用

三 、总结

一、线程同步概述

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

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

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. 【转】Java内存与垃圾回收调优

    要了解Java垃圾收集机制,先理解JVM内存模式是非常重要的.今天我们将会了解JVM内存的各个部分.如何监控以及垃圾收集调优. Java(JVM)内存模型 正如你从上面的图片看到的,JVM内存被分成多 ...

  2. [转]【技术心得】Last-Modified,Etag,Expire区别

    Last-Modified 是什么 Last-Modified 是 HttpHeader 中的资源的最后修改时间,如果带有 Last-Modified ,下一次发送 Http 请求时,将会发生带 If ...

  3. vue.js初学(二)

    1:构造器 var app = new Vue ( { //选项 }) 注意点: (1) 之后会经常用vm代表Vue实例 (2)实例化之后 需要传入一个选项对象,它可以包括数据.模板.挂载元素.方法. ...

  4. MyEclipse启动tomcat增加内存配置

    omcat增加内存在catalina.bat下 MyEclipse增加内存 设置Window->Preferences->Application Servers->Tomcat -- ...

  5. 解决 No module named PyQt5.QtWebKitWidgets

    原因:在 PyQt 5.6(+) 版本中, 新增 QtWebEngineWidgets 代替QtWebKitWidgets. 示例代码:#coding: utf-8 import sysfrom Py ...

  6. bootstrap-table 父子表入门篇

    官方文档:http://bootstrap-table.wenzhixin.net.cn/zh-cn/documentation/#多语言 一.引入js.css <!-- 引入bootstrap ...

  7. Azure VM的加速网络

    Azure的VM在经过几代发展后已经有多种硬件类型.目前Azure China的多种机型都配置了FPGA卡,可以实现网络加速.本文将介绍Azure的加速网络相关的内容. 一. 加速网络的硬件准备 下图 ...

  8. HDU1560(迭代加深搜索)

    DNA sequence Time Limit: 15000/5000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Tot ...

  9. Java: What is the difference between <init> and <clinit>?

    Stack Overflow 上的一个问题:Java: What is the difference between <init> and <clinit>?   JVM Sp ...

  10. Mina APR传输与串口传输讲解---zhengli

    原文地址:Mina Transports(Apache Mina user guide Chapter6 Transport) 1.1.1.APR传输 1.1.1.1.介绍 APR (Apache P ...