一、线程同步中的一些概念

  1.1临界区(共享区)的概念

  在多线程的环境中,可能需要共同使用一些公共资源,这些资源可能是变量,方法逻辑段等等,这些被多个线程共用的区域统称为临界区(共享区),临界区的资源不是很安全,因为线程的状态是不定的,所以可能带来的结果是临界区的资源遭到其他线程的破坏,我们必须采取策略或者措施让共享区数据在多线程的环境下保持完成性不让其受到多线程访问的破坏。

  1.2基元用户模式

  基元用户模式是指使用cpu的特殊指令来调度线程,所以这种协调调度线程是在硬件中进行的所以得出了它第一些优点:

  • 速度特别快;
  • 线程阻塞时间特别短;

  但是由于该模式中的线程可能被系统抢占,导致该模式中的线程为了获取某个资源,而浪费许多cpu时间,同时如果一直处于等待的话会导致”活锁”,也就是既浪费了内存,又浪费了cpu时间,这比下文中的死锁更可怕,那么如何利用强大的cpu时间做更多的事呢?那就引出了下面的一个模式

  1.3基元内核模式

  该模式和用户模式不同,它是windows系统自身提供的,使用了操作系统中内核函数,所以它能够阻塞线程提高了cpu的利用率,同时也带来了一个很可怕的bug,死锁,可能线程会一直阻塞导致程序的奔溃,常用的内核模式的技术例如Monitor,Mutex,等等会在下一章节介绍。本章将详细讨论锁的概念,使用方法和注意事项

  1.4原子性操作

  如果一个语句执行一个单独不可分割的指令,那么它是原子的。严格的原子操作排除了任何抢占的可能性,更方便的理解是这个值永远是最新的,在c#中原子操作如下图所示:其实要符合原子操作必须满足以下条件c#中如果是32位cpu的话,为一个少于等于32位字段赋值是原子操作,其他(自增,读,写操作)的则不是。对于64位cpu而言,操作32或64位的字段赋值都属于原子操作其他读写操作都不能属于原子操作相信大家能够理解原子的特点,所以在使用原子操作时也需要注意当前操作系统是32位或是64位cpu或者两者皆要考虑。

  1.5非阻止同步

  非阻止同步:不阻止其他线程的情况下实现同步。就是利用原子性操作实现线程间的同步,不刻意阻塞线程,减少相应线程的开销,interlocked类便是c#中非阻止同步的理念所产生的线程同步技术。

  1.6阻止同步

  阻止同步:阻止其他线程,同一时间只允许单个线程访问临界资源。其实阻止同步也是基元内核模式的特点之一。

  例如c# 中的锁机制,及mutex,monitor等都属于阻止同步,他们的根本目的是,以互斥的效果让同一时间只有一个线程能够访问共享区,其他线程必须阻止等待,直到该线程离开共享区后,才让其他一个线程访问共享区,阻止同步缺点也是容易产生死锁,但是阻止同步提高了cpu时间的利用率。

二、为何需要同步

  当多个线程同时访问某个资源,可能造成意想不到的结果。如多个线程同时访问静态资源。

    class Program
{
static void Main(string[] args)
{
//初始化10个线程1去访问num
for (int i = 0; i < 10; i++)
{
ThreadPool.QueueUserWorkItem(new WaitCallback(Run));
}
Console.ReadKey();
} static int num = 0; static void Run(object state)
{
Console.WriteLine("当前数字:{0}", ++num);
}
}

  输出如下:

  

  我们看到,num++按照逻辑,应该是1,2,3,4,5,6,7,8,9,10。这就是多个线程去访问,顺序乱套了。这时候就需要同步了。

三、原子操作同步原理

  Thread类中的VolatileRead和VolatileWrite方法:

  • VolatileWrite:当线程在共享区(临界区)传递信息时,通过此方法来原子性的写入最后一个值;
  • VolatileRead:当线程在共享区(临界区)传递信息时,通过此方法来原子性的读取第一个值;
    class Program
{
static Int32 count;//计数值,用于线程同步 (注意原子性,所以本例中使用int32)
static Int32 value;//实际运算值,用于显示计算结果 static void Main(string[] args)
{
//读线程
Thread thread2 = new Thread(new ThreadStart(Read));
thread2.Start(); //写线程
for (int i = 0; i < 10; i++)
{
Thread.Sleep(20);
Thread thread = new Thread(new ThreadStart(Write));
thread.Start();
}
Console.ReadKey();
} /// <summary>
/// 实际运算写操作
/// </summary>
private static void Write()
{
Int32 temp = 0;
for (int i = 0; i < 10; i++)
{
temp += 1;
}
//真正写入
value += temp;
Thread.VolatileWrite(ref count, 1);
} /// <summary>
/// 死循环监控读信息
/// </summary>
private static void Read()
{
while (true)
{
//死循环监听写操作线执行完毕后立刻显示操作结果
if (Thread.VolatileRead(ref count) > 0)
{
Console.WriteLine("累计计数:{1}", Thread.CurrentThread.ManagedThreadId, value);
count = 0;
}
}
}
}

  输出如下:

  

三、Volatile关键字

  Volatile关键字的本质含义是告诉编译器,声明为Volatile关键字的变量或字段都是提供给多个线程使用的。Volatile无法声明为局部变量。作为原子性的操作,Volatile关键字具有原子特性,所以线程间无法对其占有,它的值永远是最新的。

  Volatile支持的类型:

  • 引用类型;
  • 指针类型(在不安全的上下文中);
  • 类型,如 sbyte、byte、short、ushort、int、uint、char、float 和 bool;
  • 具有以下基类型之一的枚举类型:byte、sbyte、short、ushort、int 或 uint;
  • 已知为引用类型的泛型类型参数;
  • IntPtr 和 UIntPtr;
class Program
{
static volatile Int32 count;//计数值,用于线程同步 (注意原子性,所以本例中使用int32)
static Int32 value;//实际运算值,用于显示计算结果
static void Main(string[] args)
{
//开辟一个线程专门负责读value的值,这样就能看见一个计算的过程
Thread thread2 = new Thread(new ThreadStart(Read));
thread2.Start();
//开辟10个线程来负责计算,每个线程负责1000万条数据
for (int i = 0; i < 10; i++)
{
Thread.Sleep(20);
Thread thread = new Thread(new ThreadStart(Write));
thread.Start();
}
Console.ReadKey();
} /// <summary>
/// 实际运算写操作
/// </summary>
private static void Write()
{
Int32 temp = 0;
for (int i = 0; i < 10; i++)
{
temp += 1;
}
value += temp;
//告诉监听程序,我改变了,读取最新吧!
count = 1;
} /// <summary>
/// 死循环监听
/// </summary>
private static void Read()
{
while (true)
{
if (count == 1)
{
Console.WriteLine("累计计数:{1}", Thread.CurrentThread.ManagedThreadId, value);
count = 0;
}
}
}
}

  输出:

  

四、lock关键字

  lock的作用在于同一时间确保一个对象只允许一个线程访问。

  lock的语法如下:

   static object obj = new object();
lock (obj)
{
  //语句块
}

  我们使用lock来改写上面的示例:

    class Program
{
static void Main(string[] args)
{
//初始化10个线程1去访问num
for (int i = 0; i < 10; i++)
{
ThreadPool.QueueUserWorkItem(new WaitCallback(Run));
}
Console.ReadKey();
} static int num = 0; static object obj = newobject();
static void Run(object state)
{
lock (obj)
{
Console.WriteLine("当前数字:{0}", ++num);
}
}
}

  输出如下:

  

五、Monitor.Enter与Monitor.Exit

  Monitor.Enter和Monitor.Exit这个东西跟lock的作用一样。事实上。lock就是Monitor.Enter和Monitor.Exit的包装。

  下面用Monitor.Enter与Monitor.Exit来实现相同的代码:

    class Program
{
static void Main(string[] args)
{
//初始化10个线程1去访问num
for (int i = 0; i < 10; i++)
{
ThreadPool.QueueUserWorkItem(new WaitCallback(Run));
}
Console.ReadKey();
} static int num = 0; static object obj = new object(); static void Run(object state)
{
//获取排他锁
Monitor.Enter(obj); Console.WriteLine("当前数字:{0}", ++num); //释放排它锁
Monitor.Exit(obj);
}
}

六、Monitor.Wait与Monitor.Pulse

  Wait() 和 Pulse() 机制用于线程间交互:

  • Wait() 释放锁定资源,进入等待状态直到被唤醒;
  • Pulse() 和 PulseAll() 方法用来通知Wait()的线程醒来;
    class Program
{
static void Main(string[] args)
{
Thread t1 = new Thread(Run1);
Thread t2 = new Thread(Run2); t1.Start();
t1.Name = "刘备"; t2.Start();
t2.Name = "关羽"; Console.ReadKey();
} static object obj = new object(); static void Run1(object state)
{
Monitor.Enter(obj); Console.WriteLine(Thread.CurrentThread.Name + ":二弟,你上哪去了?"); Monitor.Wait(obj); //暂时释放锁,让关羽线程进入 Console.WriteLine(Thread.CurrentThread.Name + ":你混蛋!"); Monitor.Pulse(obj); //唤醒关羽线程 Monitor.Exit(obj);
} static void Run2(object state)
{ Monitor.Enter(obj); Console.WriteLine(Thread.CurrentThread.Name + ":老子跟曹操了!"); Monitor.Pulse(obj); //唤醒刘备线程
Monitor.Wait(obj); //暂停本线程 Console.WriteLine(Thread.CurrentThread.Name + ":投降吧,曹孟德当世英雄,竖子不足与谋!!"); Monitor.Exit(obj);
}
}

  输出如下:

  

七、读写锁ReadWriterLock

  写入串行,读取并行;

  如果程序中大部分都是读取数据的,那么由于读并不影响数据,ReadWriterLock类能够实现”写入串行“,”读取并行“。

  常用方法如下:

  • AcquireWriterLock: 获取写入锁; ReleaseWriterLock:释放写入锁。
  • AcquireReaderLock: 获取读锁; ReleaseReaderLock:释放读锁。
  • UpgradeToWriterLock:将读锁转为写锁;DowngradeFromWriterLock:将写锁还原为读锁。
   class Program
{
static List<string> ListStr = new List<string>();
static ReaderWriterLock rw = new System.Threading.ReaderWriterLock(); static void Main(string[] args)
{
Thread t1 = new Thread(Run1);
Thread t2 = new Thread(Run2); t1.Start();
t1.Name = "刘备"; t2.Start();
t2.Name = "关羽"; Console.ReadKey();
} static object obj = new object(); static void Run1(object state)
{
//获取写锁2秒
rw.AcquireWriterLock(2000);
Console.WriteLine(Thread.CurrentThread.Name + "正在写入!");
ListStr.Add("曹操混蛋");
ListStr.Add("孙权王八蛋");
Thread.Sleep(1200);
ListStr.Add("周瑜个臭小子");
rw.ReleaseWriterLock(); } //此方法异常,超时,因为写入时不允许读(那么不用测也能猜到更加不允许写咯)
static void Run2(object state)
{
//获取读锁1秒
rw.AcquireReaderLock(1000);
Console.WriteLine(Thread.CurrentThread.Name + "正在读取!");
foreach (string str in ListStr)
{
Console.WriteLine(str);
}
rw.ReleaseReaderLock();
}
}

  异常如下:

  

  下面是读取并行的例子:

    class Program
{
static List<string> ListStr = new List<string>();
static ReaderWriterLock rw = new System.Threading.ReaderWriterLock(); static void Main(string[] args)
{
ListStr.Add("貂蝉");
ListStr.Add("西施");
ListStr.Add("王昭君");
Thread t1 = new Thread(Run1);
Thread t2 = new Thread(Run2); t1.Start();
t1.Name = "刘备"; t2.Start();
t2.Name = "关羽"; Console.ReadKey();
} static object obj = new object(); static void Run1(object state)
{
//获取写锁2秒
rw.AcquireReaderLock(2000);
Console.WriteLine(Thread.CurrentThread.Name + "正在读取!");
foreach (string str in ListStr)
{
Console.WriteLine(Thread.CurrentThread.Name + "在读:" + str);
}
rw.ReleaseReaderLock(); } //此方法异常,超时,因为写入时不允许读(那么不用测也能猜到更加不允许写咯)
static void Run2(object state)
{
//获取读锁1秒
rw.AcquireReaderLock(1000);
Console.WriteLine(Thread.CurrentThread.Name + "正在读取!");
foreach (string str in ListStr)
{
Console.WriteLine(Thread.CurrentThread.Name + "在读:" + str);
}
rw.ReleaseReaderLock();
}
}

  输出如下:

  

  总结:写入锁与任何锁都不兼容,读取锁与读取锁可以兼容。

锁机制与原子操作 <第四篇>的更多相关文章

  1. 转载 锁机制与原子操作 <第四篇>

    一.线程同步中的一些概念 1.1临界区(共享区)的概念 在多线程的环境中,可能需要共同使用一些公共资源,这些资源可能是变量,方法逻辑段等等,这些被多个线程共用的区域统称为临界区(共享区),临界区的资源 ...

  2. Linux内核中锁机制之原子操作、自旋锁

    很多人会问这样的问题,Linux内核中提供了各式各样的同步锁机制到底有何作用?追根到底其实是由于操作系统中存在多进程对共享资源的并发访问,从而引起了进程间的竞态.这其中包括了我们所熟知的SMP系统,多 ...

  3. 大话Linux内核中锁机制之原子操作、自旋锁

    转至:http://blog.sina.com.cn/s/blog_6d7fa49b01014q7p.html 很多人会问这样的问题,Linux内核中提供了各式各样的同步锁机制到底有何作用?追根到底其 ...

  4. 大话Linux内核中锁机制之原子操作、自旋锁【转】

    转自:http://blog.sina.com.cn/s/blog_6d7fa49b01014q7p.html 多人会问这样的问题,Linux内核中提供了各式各样的同步锁机制到底有何作用?追根到底其实 ...

  5. Atitit.软件与编程语言中的锁机制原理attilax总结

    Atitit.软件与编程语言中的锁机制原理attilax总结 1. 用途 (Db,业务数据加锁,并发操作加锁.1 2. 锁得类型 排它锁 "互斥锁 共享锁 乐观锁与悲观锁1 2.1. 自旋锁 ...

  6. JAVA面试常见问题之锁机制篇

    1.说说线程安全问题,什么是线程安全,如何保证线程安全 线程安全:就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用.不 ...

  7. 「MySQL高级篇」MySQL锁机制 && 事务

    大家好,我是melo,一名大三后台练习生,最近赶在春招前整理整理发过的博客~! 引言 锁锁锁,到哪到离不开这桩琐事,并发琐事,redis琐事,如今是MySQL琐事,这其中琐事,还跟MySQL另一个重要 ...

  8. posix第二篇-----linux 锁机制

    1 简介 锁机制(lock) 是多线程编程中最常用的同步机制,用来对多线程间共享的临界区(Critical Section) 进行保护. Pthreads提供了多种锁机制,常见的有: 1) Mutex ...

  9. InnoDB的锁机制浅析(四)—不同SQL的加锁状况

    不同SQL的加锁状况 文章总共分为五个部分: InnoDB的锁机制浅析(一)-基本概念/兼容矩阵 InnoDB的锁机制浅析(二)-探索InnoDB中的锁(Record锁/Gap锁/Next-key锁/ ...

随机推荐

  1. Keil C51处理可重入函数问题的探讨

    在程序设计中,变量具体可以分为四种类型:全局变量.静态全局变量.局部变量.静态局部变量.这几种变量类型对函数的可重入产生的重大的影响,因为不同的编译器采用不同的策略. 针对51的存储区有限,keil ...

  2. Android的init过程详解(一)(转)

    本文使用的软件版本 Android:4.2.2 Linux内核:3.1.10 本文及后续几篇文章将对Android的初始化(init)过程进行详细地.剥丝抽茧式地分析,并且在其中穿插了大量的知识,希望 ...

  3. SCOI2014省选总结

    这一次省选,主要是抱着玩的心态去的,如同高二的那些大神高一的心态一样,只记得在省选之前我们一直在说,这一次我们的目标,就是不爆0,最后也如愿以偿的实现了. 首先,请允许我吐槽一下day1.....da ...

  4. python高级编程之装饰器04

    from __future__ import with_statement # -*- coding: utf-8 -*- # python:2.x __author__ = 'Administrat ...

  5. Guice 学习(六)使用Provider注入服务( Provider Inject Service)

    1.定义接口 package com.guice.providerInject; import com.google.inject.ProvidedBy; public interface Servi ...

  6. SqlServer存储过程传入Table参数

    今天是周日,刚好有空闲时间整理一下这些天工作业务中遇到的问题. 有时候我们有这样一个需求,就是在后台中传过来一个IList<类>的泛型集合数据,该集合是某个类的实例集合体,然后将该集合中的 ...

  7. SQL Server数据库的操作流程和连接的简单介绍

    学习ADO,免不了要跟数据库打交道,对于初学者来说,如果不整理一下整个流程,那么可能会出现很多的问题,下面简单的介绍数据库的操作流程. 1.     我们最终操作的对像是数据表,在操作数据表之前我们先 ...

  8. MySQL主从同步、读写分离配置步骤

    现在使用的两台服务器已经安装了MySQL,全是rpm包装的,能正常使用. 为了避免不必要的麻烦,主从服务器MySQL版本尽量保持一致; 环境:192.168.0.1 (Master) 192.168. ...

  9. Spark集群搭建简要

    Spark集群搭建 1 Spark编译 1.1 下载源代码 git clone git://github.com/apache/spark.git -b branch-1.6 1.2 修改pom文件 ...

  10. FZU1327 优先队列

    Problem 1327 Blocks of Stones II Accept: 318    Submit: 881Time Limit: 1000 mSec    Memory Limit : 3 ...