zoukankan      html  css  js  c++  java
  • 转贴:谈谈多线程的思维方式

    Posted on 2009-04-24 08:53 Kevin-moon

    前段时间仔细看过些关于多线程方面的资料,项目中用到线程的地方也不少,可是,当看了Jeffrey的一篇关于锁的文章后,发现自己虽然一直都在使用多线程,但是缺少了做多线程编程需要的思维!所以想从Jeffrey的Optex(锁)入手,来谈谈我从其中体会的东西。

       在NET中,我们用的最多的锁机制就是lock,用起来很简单,短短几行程序就可以实现,例如:
    Lock 's Code
    public class TestThreading
    {
        private System.Object lockThis = new System.Object();
        public void Function()
        {
            lock (lockThis)
            {
                // Access thread-sensitive resources.
            }
        }
    }其实我们也明白,lock并不是锁,而是MS提供的一个简便式的写法,真正实现的是Monitor类中的Enter和Exit方法,既然提到了Monitor类也就说下有个需要注意的地方:
        Pulse和PulseAll方法,这两个方法就是把锁状态将要改变的消息通知给等待队列中的线程,不过这时如果等待队列中没有线程,那么该方法就会一直等待下去,直到有等待的线程进入队列,也就是说该方法可能造成类试死锁的情况出现。

        上面的lock + 线程(Thread和ThreadPool) = 多线程编程(N%)!?
        对于该公式我曾经的N是80,现在是20。其中有很多东西影响我,让我从80->20,下面的Optex就是一个入口点。


    Optex 's Code
    public sealed class Optex : IDisposable {
       private Int32 m_Waiters = 0;
       private Semaphore m_WaiterLock = new Semaphore(0, Int32.MaxValue);

       public Optex() { }
       public void Dispose() {
          if (m_WaiterLock != null)
          { 
             m_WaiterLock.Close(); 
             m_WaiterLock = null;
          } 
       }

       public void Enter() {
          Thread.BeginCriticalRegion();
          // Add ourself to the set of threads interested in the Optex
          if (Interlocked.Increment(ref m_Waiters) == 1) {
             // If we were the first thread to show interest, we got it.
             return;
          }

          // Another thread has the Optex, we need to wait for it
          m_WaiterLock.WaitOne();
          // When WaitOne returns, this thread now has the Optex
       }

       public void Exit() {
          // Subtract ourself from the set of threads interested in the Optex
          if (Interlocked.Decrement(ref m_Waiters) > 0) {
             // Other threads are waiting, wake 1 of them
             m_WaiterLock.Release(1);
          }
          Thread.EndCriticalRegion();
       }
    }   看完上面的代码,让我增加了两点认识: 
       1、Thread.BeginCriticalRegion()和Thread.EndCriticalRegion();         
          因为这段时间正好看了一本多线程编程的书,既然将上面方法认为是进入临界区和退出临界区,对于临界区而言,进入该区的数据,在没有退出之前,如果临界区外的程序需要使用它,那么就必须出于等待。所以觉得已经使用临界区,为什么还要使用Semaphore?! 
       可是,MS只是取了个相同的名字,做的事情完全不同,上面两个方法完全没有临界区的概念,它只是设置一个区域(Begin到End之间),表示该区域内发生线程中断或未处理的异常会影响整个应用程序域。

    2、m_Waiters的作用

            一开始以为在Enter的时候,直接写上:
                m_WaiterLock.WaitOne();

                  Exit的时候,写上: 
                       m_WaiterLock.Release(1);              
           这样就可以了。m_Waiters有什么意义?!            
       优化性能,Semaphore是内核对象,我们都知道,要尽量少的进入内核模式,因为这是很消耗性能,所以尽量少的使用内核对象。m_Waiters的意义就在这里,如果只有一个线程使用该锁对象的时候,是不需要去获取和释放的。 OK,上述的东西都是铺垫,铺完了也就进入主题了!

    多线程的思维


    优化的Optex
    namespace ThreadConcurrent.Lock
    {
        public sealed class Optex : IDisposable
        {
            /// <summary>
            /// 琐的状态
            /// </summary>
            private Int32 m_LockState = c_lsFree;

            /// <summary>
            /// 自由状态
            /// </summary>
            private const Int32 c_lsFree = 0x00000000;
            
            /// <summary>
            /// 被拥有状态
            /// </summary>
            private const Int32 c_lsOwned = 0x00000001;
            
            /// <summary>
            /// 等待的线程数
            /// </summary>
            private const Int32 c_1Waiter = 0x00000002;

            private Semaphore m_WaiterLock = new Semaphore(0, Int32.MaxValue);

            #region 构造函数

            /// <summary>
            /// 
            /// </summary>
            public Optex() { }

            #endregion

            /// <summary>
            /// 请求锁
            /// </summary>
            public void Enter()
            {
                Thread.BeginCriticalRegion();
                while (true)
                {
                    Int32 ls = InterlockedOr(ref m_LockState, c_lsOwned);

                    //自由状态
                    if ((ls & c_lsOwned) == c_lsFree) return;

                    // 增加等待的线程数
                    if (IfThen(ref m_LockState, ls, ls + c_1Waiter))
                    {
                        m_WaiterLock.WaitOne();
                    }
                }
            }

            public void Exit()
            {
                // 释放琐
                Int32 ls = InterlockedAnd(ref m_LockState, ~c_lsOwned);

                //无等待的线程
                if (ls == c_lsOwned)
                {
                }
                else
                {
                    ls &= ~c_lsOwned;
                    if (IfThen(ref m_LockState, ls & ~c_lsOwned, ls - c_1Waiter))
                    {
                        m_WaiterLock.Release(1);
                    }
                    else
                    {
                    }
                }
                Thread.EndCriticalRegion();
            }

            #region 原子化操作

            /// <summary>
            /// 与操作
            /// </summary>
            /// <param name="target"></param>
            /// <param name="with"></param>
            /// <returns></returns>
            private static Int32 InterlockedAnd(ref Int32 target, Int32 with)
            {
                Int32 i, j = target;
                do
                {
                    i = j;
                    j = Interlocked.CompareExchange(ref target, i & with, i);
                } while (i != j);
                return j;
            }

            /// <summary>
            /// 或操作
            /// </summary>
            /// <param name="target"></param>
            /// <param name="with"></param>
            /// <returns></returns>
            private static Int32 InterlockedOr(ref Int32 target, Int32 with)
            {
                Int32 i, j = target;
                do
                {
                    i = j;
                    j = Interlocked.CompareExchange(ref target, i | with, i);
                } while (i != j);
                return j;
            }

            #endregion

            private static Boolean IfThen(ref Int32 val, Int32 @if, Int32 then)
            {
                return (Interlocked.CompareExchange(ref val, @then, @if) == @if);
            }

            private static Boolean IfThen(ref Int32 val, Int32 @if, Int32 then, out Int32 prevVal)
            {
                prevVal = Interlocked.CompareExchange(ref val, @then, @if);
                return (prevVal == @if);
            }

            /// <summary>
            /// 释放资源
            /// </summary>
            public void Dispose()
            {
                if (m_WaiterLock != null)
                {
                    m_WaiterLock.Close();
                    m_WaiterLock = null;
                }
            }
        }
    }     对于上面的这个代码,我晕眩了好一段时间,不过当我真正理解的时候,从晕眩中学到了做多线程编程应该具备的思维方式。
    首先从简单的理解开始谈,
    1、原子化操作
         对于InterLocked类,曾经也知道,但是却用的很少,不过从该代码中知道,在多线程的编程中对共享数据的写入操作,一定要达到原子性。至于如何做到这点,InterlockedAnd和InterlockedOr做了很好的诠释:
         While循环的目的就是保证target值以最新的值做与操作,如果传入的值在执行的过程被其他线程改变的话,那么是不会退出该循环的,并会利用改变后的值重新做次与操作。

    2、理解Enter和Exit
         这两个方法很难写出来解释,用图是最清晰的。
     

    曾经的晕眩:
    1、Enter方法中为什么存在循环,为什么不是执行完waitone就结束,必须m_lockState等于c_IsFree的时候才结束?
         线程的执行并不完全按照先前排好的顺序去执行,有时会发生一些特殊的情况来使改变线程的调度顺序,所以就可能会出现上图灰色部分的情况,则为了解决该可能发生的问题(概率很小)循环机制就出现了。
    2、为什么在WaitOne和Release之前,除了增加和减少等待者外,还需要判断m_lockstate是否改变(进入Enter到执行Waitone前的这段时间)?
    一般性的思维:
     

    该程序的思维:

          这样做的好处就是尽量少的操作内核对象,提高性能!

          多线程编程虽然复杂,但是我觉得很有意思和挑战性,而且随着硬件的发展,多线程编程会更加重要,既然已经上路就让我们走到尽头!

  • 相关阅读:
    Kafka中的数据清理(logdeletion)
    genymotion虚拟器笔记
    layui hover显示图片
    vue 导出到excel
    el-tree知识积累
    js 含有对象的数组去重
    苏宁易购价格爬取(golang)
    vue+elementui+beego笔记
    vue笔记
    beego笔记
  • 原文地址:https://www.cnblogs.com/dayw/p/1742728.html
Copyright © 2011-2022 走看看