• 巧用Dictionary对象实现线程安全类(实例)


         在前一面篇文章[设计安全的多线程应用程序(线程安全)]中,我们讲了,什么是线程安全,列举一些常见的线程安全和非线程安全的情况。还没对线程安全了解的同学请点上面的链接。现在我们来看线程不安全的本质是什么。

       我们来想想在单线程的情况下安全,为什么在多线程的情况下是不安全的呢?无非就是因为多线程是并行执行,当然并行执行本身是没有错的。如果这些并行跑起来的线程,出现下列情况时可能会导致线程不安全。第一,争抢独占的资源如同时写一个文件,对独占的资源的排它锁。第二,程序中的全局对象,如类中对静态成员,被多个线程同时更新(修改或者删除),会产生脏的数据或者是其它线程得不到正确的数据的后果;同一个类实例的非静态成员,被此实例中的函数使用(其中有修改实例成员的功能),此时两个以上线程同时使用时,就可能使类成员的状态对同一个线程的使用的不一致性。

         由此,可以看出,线程安全的本质就是,要保护全局对象,在使用时只能被一个线程使用(包括读,写,删除)(注意,对全局对象只限于只读,那么是线程安全的)。解决的方法无非就是两种,第一,把这个对象或者资源锁住,即等我用完了,其它线程才能再能,否则其它线程等待,这也是最常见的方式。第二,给每个线程分配属于只属于自己的"全局对象"(这样听起来好像有点别扭)。

         要注意的一点是,很多语句的类库中的类,都没在实现线程安全,这是因为基于性能的考虑,这样的情况下,就让我们程序员自己来决定实现线程安全。


    下面这两种设计线程安全类的例子:

     第一,用lock语句(在c#中)

    1,锁住同一个实例中的全局资源(同一实例中非静态成员)

    public void Function()
    {
        System.Object lockThis 
    = new System.Object();
        
    lock(lockThis)
        {
            
    // Access thread-sensitive resources.
        }
    }

    2,锁住类中静态成员

    static StringBuilder CstrBuild = new StringBuilder();
    static System.Object lockThis = new System.Object();
    public void Function()
    {    
        
    lock(lockThis)
        {
            CstrBuild.Append(
    "test");
        }
    }

     第二,使用给每一个线程分配自己的“全局对象”的方法,实现一个带有缓存的写文件功能的线程安全的类(避免每条数据写一次,造成IO瓶颈)。

    它的原则是给每个线程分配自己的“全局对象”。

    实现功能:

    当每个线程的StringBuilder对象,到达一定长度 const int CBufferLenght = 10000*38后才写入文件。 

    步骤是:

    1, 声明一个分局对象集合,我用了Dictionary对象,key是线程id,value就是它的“全局对象”

    static Dictionary<int, StringBuilder> CLBufferOfEThread = new Dictionary<int, StringBuilder>();

    2,需要注意的是,当第一次将数据加入到全局集合对象Dictionary时,需要锁定Dictionary对象

    lock (lockBuffer)
     {
      CLBufferOfEThread[Thread.CurrentThread.ManagedThreadId] 
    = new StringBuilder(pstrContent);
    }

    3, 给Dictionary对象赋值时,无需使用lock(因为它是线程独占的全局对象,不会冲突,我给每个线程id分配了自己的“全局对象”,即集合中的某个元素)

    CLBufferOfEThread[Thread.CurrentThread.ManagedThreadId].AppendLine(pstrContent);

    4,在虚构函数处理还未写入文件的数据

    ~StreamWriteWithBuffer()
            {
                
    foreach (StringBuilder valus in CLBufferOfEThread.Values)
                {
                    
    if (valus.Length > 0 && CLimpIDfileName.Length != 0)
                    {
                        
    using (StreamWriter logWrite = new StreamWriter(CLimpIDfileName, true, Encoding.Default))
                        {
                            logWrite.WriteLine(valus.ToString());
                        }
                        valus.Remove(
    0, valus.Length);
                    }
                }
            }

    下面就是具体的代码,

    调用代码:

    StreamWriteWithBuffer StreamWriteWithBuffer = new StreamWriteWithBuffer();
            /// <summary>
            
    /// Writing content once a content was created
            
    /// </summary>
            
    /// <param name="pstrLogName">limpid file name</param>
            
    /// <param name="pstrContent"></param>
            public void WriteLimpIDLog(string pstrContent)
            {
                
    try
                {

                        StreamWriteWithBuffer.WriteLine("Stats/" + CLimpIDfileName, pstrContent);
                }
                
    catch (Exception ex)
                {
                    Console.WriteLine(
    "Exception of log file:" + ex.Message);
                }
            }


    类实现:


    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.IO;
    using System.Threading;

    namespace Project.Common
    {
        
    /// <summary>
        
    /// the class for writing something into a file,it can have a buffer to store the data which need to write before write to file really.
        
    /// </summary>
        class StreamWriteWithBuffer : IDisposable
        {
            
    string CLimpIDfileName = string.Empty;
            
    const int CBufferLenght = 10000*38;        
            
    //static StringBuilder CBuffer = new StringBuilder();

            
    static Dictionary<int, StringBuilder> CLBufferOfEThread = new Dictionary<int, StringBuilder>();
     
            
    public StreamWriteWithBuffer(string pfileName)
            {
                CLimpIDfileName 
    = pfileName;
                            
            }
            
    public StreamWriteWithBuffer()
            {
            }

            
    ~StreamWriteWithBuffer()
            {
                
    foreach (StringBuilder valus in CLBufferOfEThread.Values)
                {
                    
    if (valus.Length > 0 && CLimpIDfileName.Length != 0)
                    {
                        
    using (StreamWriter logWrite = new StreamWriter(CLimpIDfileName, true, Encoding.Default))
                        {
                            logWrite.WriteLine(valus.ToString());
                        }
                        valus.Remove(
    0, valus.Length);
                    }
                }
            }

            private static Object lockWrite = new Object();

            private static Object lockBuffer = new Object();

            public void WriteLine(string pLimpIDfileName, string pstrContent)
            {
                
    try
                {
                    
    if (CLBufferOfEThread.ContainsKey(Thread.CurrentThread.ManagedThreadId))
                    {
                        
    if (CLBufferOfEThread[Thread.CurrentThread.ManagedThreadId].Length < CBufferLenght)
                        {
                            CLBufferOfEThread[Thread.CurrentThread.ManagedThreadId].AppendLine(pstrContent);
                            CLimpIDfileName 
    = pLimpIDfileName;
                        }
                        
    else
                        {
                            
    lock (lockWrite)
                            {
                                
    using (StreamWriter logWrite = new StreamWriter(pLimpIDfileName, true, Encoding.Default))
                                {
                                    logWrite.WriteLine(CLBufferOfEThread[Thread.CurrentThread.ManagedThreadId].ToString());
                                }
                            }

                            CLBufferOfEThread[Thread.CurrentThread.ManagedThreadId].Remove(
    0, CLBufferOfEThread[Thread.CurrentThread.ManagedThreadId].Length);
                        }
                    }
                    
    else
                    {
                        
    lock (lockBuffer)
                        {
                            CLBufferOfEThread[Thread.CurrentThread.ManagedThreadId] 
    = new StringBuilder(pstrContent);
                        }
                    }
                }
                
    catch (Exception ex)
                {

                    Console.WriteLine(
    "Exception of limpID log file:" + ex);
                }
            }

            
    public void Dispose()
            {             
            }
        }
    }

    总之,线程安全除了在设计时要尽量避免以上的情况之外,还要反复的测试你的类,或者程序,才能更终实现线程安全类。

  • 相关阅读:
    celery的使用
    DOM操作
    js动画
    列表案例
    背景案例
    背景属性连写
    背景属性
    链接导航案例
    链接伪类
    优先权之权重会叠加
  • 原文地址:https://www.cnblogs.com/luyinghuai/p/1279665.html
Copyright © 2020-2023  润新知