巧用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()
        {             
        }
    }
}

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

原文地址:https://www.cnblogs.com/luyinghuai/p/1279665.html