锁机制与原子操作 <第四篇>

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

  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();
        }
    }

  输出如下:

  

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

时间: 2024-08-29 06:16:34

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

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

转至:http://blog.sina.com.cn/s/blog_6d7fa49b01014q7p.html 很多人会问这样的问题,Linux内核中提供了各式各样的同步锁机制到底有何作用?追根到底其实是由于操作系统中存在多进程对共享资源的并发访问,从而引起了进程间的竞态.这其中包括了我们所熟知的SMP系统,多核间的相互竞争资源,单CPU之间的相互竞争,中断和进程间的相互抢占等诸多问题. 通常情况下,如图1所示,对于一段程序,我们的理想是总是美好的,希望它能够这样执行:进程1先对临界区完成操作,

Sql Server之旅——第十四站 深入的探讨锁机制

原文:Sql Server之旅--第十四站 深入的探讨锁机制 上一篇我只是做了一个堆表让大家初步的认识到锁的痉挛状态,但是在现实世界上并没有这么简单的事情,起码我的表不会没有索引对吧,,,还 有就是我的表一定会有很多的连接过来,10:1的读写,很多码农可能都会遇到类似神乎其神的死锁,卡住,读不出来,插不进入等等神仙的事情导致性 能低下,这篇我们一起来探讨下. 一: 当select遇到性能低下的update会怎么样? 1. 还是使用原始的person表,插入6条数据,由于是4000字节,所以两条数

[数据库事务与锁]详解四: 数据库的锁机制

注明: 本文转载自http://www.hollischuang.com/archives/898 数据库的读现象浅析中介绍过,在并发访问情况下,可能会出现脏读.不可重复读和幻读等读现象,为了应对这些问题,主流数据库都提供了锁机制,并引入了事务隔离级别的概念. 并发控制 在计算机科学,特别是程序设计.操作系统.多处理机和数据库等领域,并发控制(Concurrency control)是确保及时纠正由并发操作导致的错误的一种机制. 数据库管理系统(DBMS)中的并发控制的任务是确保在多个事务同时存

数据库并发事务控制四:postgresql数据库的锁机制

并发控制是DBMS的关键技术,并发控制技术也称为同步机制,其实现通常依赖于底层的并发控制机制.操作系统提供了多种同步对象,如事件 Event.互斥锁 Mutex和条件变量 Cond.信号量Semaphore.读写锁 RWLock.自旋锁 Spinlock等.数据库管理系统自己实现封锁主要是考虑: 锁语义加强:OS只提供排它锁.为了提高并发度,数据库至少需要共享锁和排它锁,即读锁和写锁: 锁的功能增强:数据库提供视图监测封锁情况和进行死锁检测: 可靠性的考虑:例如,在某个持锁进程任意一点回滚时,数

数据库并发事务控制四:postgresql数据库的锁机制二:表锁

在博文<数据库并发事务控制四:postgresql数据库的锁机制 > http://blog.csdn.net/beiigang/article/details/43302947 中后面提到: 常规锁机制可以参考pg的官方手册,章节和内容见下面 13.3. Explicit Locking http://www.postgresql.org/docs/9.4/static/explicit-locking.html 这节分为:表锁.行锁.页锁.死锁.Advisory锁(这个名字怎么翻译好???

MySQL基础篇(06):事务管理,锁机制案例详解

本文源码:GitHub·点这里 || GitEE·点这里 一.锁概念简介 1.基础描述 锁机制核心功能是用来协调多个会话中多线程并发访问相同资源时,资源的占用问题.锁机制是一个非常大的模块,贯彻MySQL的几大核心难点模块:索引,锁机制,事务.这里是基于MySQL5.6演示的几种典型场景,对面MySQL这几块问题时,有分析流程和思路是比较关键的.在MySQL中常见这些锁概念:共享读锁.排它写锁 ; 表锁.行锁.间隙锁. 2.存储引擎和锁 MyISAM引擎:基于读写两种模式,支持表级锁 ; Inn

并发编程(四):也谈谈数据库的锁机制

http://www.2cto.com/database/201403/286730.html 1. 数据库并发的问题 数据库带来的并发问题包括: 1. 丢失更新. 2. 未确认的相关性(脏读). 3. 不一致的分析(非重复读). 4. 幻像读. 详细描述如下: 1.1.丢失更新 当两个或多个事务选择同一行,然后基于最初选定的值更新该行时,会发生丢失更新问题.每个事务都不知道其它事务的存在.最后的更新将重写由其它事务所做的更新,这将导致数据丢失. e.g.事务A和事务B同时修改某行的值, 事务A

python3.4多线程实现同步的四种方式(锁机制、条件变量、信号量和同步队列)

临界资源即那些一次只能被一个线程访问的资源,典型例子就是打印机,它一次只能被一个程序用来执行打印功能,因为不能多个线程同时操作,而访问这部分资源的代码通常称之为临界区. threading的Lock类,用该类的acquire函数进行加锁,用realease函数进行解锁 import threading import time class Num: def __init__(self): self.num = 0 self.lock = threading.Lock() def add(self)

mysql中的锁机制之概念篇

锁的概念 ①.锁,在现实生活中是为我们想要隐藏于外界所使用的一种工具. ②.在计算机中,是协调多个进程或线程并发访问某一资源的一种机制. ③.在数据库当中,除了传统的计算资源(CPU.RAM.I/O等等)的争用之外,数据也是一种供许多用户共享访问的资源. ④.如何保证数据并发访问的一致性.有效性,是所有数据库必须解决的一个问题. ⑤.锁的冲突也是影响数据库并发访问性能的一个重要因素. MySQL锁的概述 相对于其它数据库而言,MySQL的锁机制比较简单,其最 显著的特点是不同的存储引擎支持不同的