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

  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. [科普]MinGW vs MinGW-W64及其它

    转载:http://tieba.baidu.com/p/3186234212?pid=54372018139&cid=#54372018139 这里也转一下吧. 部分参照备忘录原文: bitb ...

  2. 车祸 shit

    今天上班的时候就觉得右眼在那跳,妈的,果不其然,回家路上自行车也跟人家撞上了,郁闷,裤子也坏了,腿也伤了.我还没反应过来,撞一起的是个女的,十七八岁吧,郁闷,什么破自行车.强烈呼吁不要去买小自行车了, ...

  3. HDU4171--bfs+树

    第一开始想成了DP.尼玛后来才发现只有N条边,那就简单了.. 从起点S遍历整棵树,从某点跳出来回到终点T,问最短路长度.然而从某点跳出时走过的路径是一个定值.... 长度为整棵树的边长和sum*2-d ...

  4. JSTL解析——003——core标签库02

    上一节主要讲解了<c:if/><c:choose/><c:when/><c:otherwise><c:out/>标签的使用,下面继续讲解其它 ...

  5. [RxJS] Reactive Programming - What is RxJS?

    First thing need to understand is, Reactive programming is dealing with the event stream. Event stre ...

  6. python 弄github代码库列表

    1.底        项目要求,征求github的repo的api,为了能够提取repo对数据进行分析. 研究一天.最终克服该问题,較低下.     由于github的那个显示repo的api,列出了 ...

  7. Android窗口管理服务WindowManagerService对壁纸窗口(Wallpaper Window)的管理分析

    文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/8550820 Android系统中,壁纸窗口和输 ...

  8. js 代码命名规范系列

    在微博上看到一个段子 “老子哪天出任ceo迎娶白富美走上人生巅峰之后,一定要雇两个长腿大熊的妹子.一个帮我想变量名字,一个帮我想git commit的message!” 可以看出 命名方方面面的问题困 ...

  9. 自定义tableviewCell的分割线

    第一种:addsubview UIView *line = [[UIView alloc]initWithFrame:CGRectMake(10, cellH-0.5, DEVW-10, 0.5)]; ...

  10. 在现代渲染API下,封装跨平台渲染框架的尝试 - 资源管理

    小生资历浅薄,不讨论该主题的重要性与未来的意义,只是个人兴趣爱好平日对这个问题思考了很多,总觉得要写点东西记录下来.框架还没有定型,只是记录自己设计的过程. 系统要跨平台,首先得将平台相关的实现与平台 ...