金沙国际官网_金沙国际平台登录

因为这个金沙国际官网_金沙国际平台登录网站与很多的大型澳门赌场都有合作,金沙国际官网_金沙国际平台登录尽职尽责,高效执行,保持好奇心,不断学习,追求卓越,点击进入金沙国际官网_金沙国际平台登录马上体验吧,所以现在也正式地开始了营业。

您的位置:金沙国际官网 > 编程 > 如何编写一个简单的依赖注入容器,洗礼灵魂

如何编写一个简单的依赖注入容器,洗礼灵魂

发布时间:2019-11-04 04:08编辑:编程浏览(170)

    一、什么是TransactionScope?

      TransactionScope即范围事务(类似数据库中的事务),保证事务声明范围内的一切数据修改操作状态一致性,要么全部成功,要么全部失败回滚.

      MSDN:如果在事务范围内未不发生任何异常 (即之间的初始化 TransactionScope 对象并调用其 Dispose 方法),则范围所参与的事务可以继续,否则参与到其中的事务将回滚。
          当应用程序完成所有工作时它想要在事务中执行,应调用 Complete 方法一次,以通知该事务管理器是可接受(此时事务并未提交),即可提交事务,未能调用此方法中止事务。
          调用 Dispose 方法将标记事务范围的末尾。 在调用此方法之后所发生的异常不会影响事务。

    随着大规模的项目越来越多,许多项目都引入了依赖注入框架,其中最流行的有Castle Windsor, Autofac和Unity Container。
    微软在最新版的Asp.Net Core中自带了依赖注入的功能,有兴趣可以查看这里。
    关于什么是依赖注入容器网上已经有很多的文章介绍,这里我将重点讲述如何实现一个自己的容器,可以帮助你理解依赖注入的原理。

    线程(下)

    二、TransactionScope有什么用?

      假如现在有一个需求:实现一个下单功能,主要业务包含扣减商品库存、扣减用户账户余额、生成订单记录以及记录日志。为了保证数据一致性我们通常的做法就是在数据库建一个下订单的事务,然后程序调用事务传入相应的参数即可。那么问题来了,如果用户的账户数据跟订单数据分别处于不同的数据库,就没法在同一个数据库事务里完成所有任务,也就没法保证数据的一致性。
      最近由于业务的变更公司改用MySQL数据库,处理数据变更时习惯性先写事务,写的时候发现现有数据库中一个事务都没有,于是去问java组,不使用事务怎么保证数据的一致性?得到的答复是:事务是什么鬼,spring帮我们解决所有问题...。立马就懵逼了,.net中没听说有Spring啊(据说有类似的框架),虽然可以考虑使用仓储加工作单元来解决,但是感觉好麻烦的样子,后来寻找解决方案时发现了TransactionScope。

    容器的构想

    在编写容器之前,应该先想好这个容器如何使用。
    容器允许注册服务和实现类型,允许从服务类型得出服务的实例,它的使用代码应该像

    var container = new Container();
    
    container.Register<MyLogger, ILogger>();
    
    var logger = container.Resolve<ILogger>();
    

    7.同步锁

    这个例子很经典,实话说,这个例子我是直接照搬前辈的,并不是原创,不过真的也很有意思,请看:

    #!usr/bin/env python
    #-*- coding:utf-8 -*-
    
    # author:yangva
    
    import threading,time
    
    number = 100
    def subnum():
        global number
        number -= 1
    
    threads = []
    for i in range(100):
        t = threading.Thread(target=subnum,args=[])
        t.start()
        threads.append(t)
    
    for i in threads:
        i.join()
    
    print(number)
    

     

    这段代码的意思是,用一百个线程去减1,以此让变量number为100的变为0

     

    结果:

     

    图片 1

     

    那么我稍微的改下代码看看: 

     

    #!usr/bin/env python
    #-*- coding:utf-8 -*-
    
    # author:yangva
    
    import threading,time
    
    number = 100
    def subnum():
        global number
        temp = number
        time.sleep(0.2)
        number = temp -1
    
    threads = []
    for i in range(100):
        t = threading.Thread(target=subnum,args=[])
        t.start()
        threads.append(t)
    
    for i in threads:
        i.join()
    
    print(number)
    

      

    并没有很大的改变对吧,只是加了一个临时变量,并且中途停顿了0.2s而已。

    而这个结果就不一样了:

    图片 2

     

    这里我先说下,time.sleep(0.2)是我故意加的,就是要体现这个效果,如果你的电脑不加sleep就已经出现这个情况了那么你就不用加了,这咋回事呢?这就是线程共用数据的潜在危险性,因为线程都是抢着CPU资源在运行,只要发现有空隙就各自抢着跑,所以在这停顿的0.2s时间中,就会有新的线程抢到机会开始运行,那么一百个线程就有一百个线程在抢机会运行,抢到的时间都是在temp还没有减1的值,也就是100,所以大部分的线程都抢到了100,然后减1,少部分线程没抢到,抢到已经减了一次的99,这就是为什么会是99的原因。而这个抢占的时间和结果并不是根本的原因,究其根本还是因为电脑的配置问题了,配置越好的话,这种越不容易发生,因为一个线程抢到CPU资源后一直在运行,其他的线程在短暂的时间里得不到机会。

     

    而为什么number -= 1,不借助其他变量的写法就没事呢?因为numebr -= 1其实是两个步骤,减1并重新赋值给number,这个动作太快,所以根本没给其他的线程机会。

     

    图解: 

    图片 3

     

    那么这个问题我们怎么解决呢,在以后的开发中绝对会遇到这种情况对吧,这个可以解决呢?根据上面的讲解,有人会想到用join,而前面已经提过了join会使多线程变成串行,失去了多线程的用意。这个到底怎么解决呢,用同步锁

    同步锁:当运行开始加锁,防止其他线程索取,当运行结束释放锁,让其他线程继续

     

    #!usr/bin/env python
    #-*- coding:utf-8 -*-
    
    # author:yangva
    import threading,time
    
    r = threading.Lock() #创建同步锁对象
    
    number = 100
    def subnum():
        global number
        r.acquire() #加锁
        temp = number
        time.sleep(0.2)
        number = temp - 1
        r.release() #释放
    
    
    threads = []
    for i in range(100):
        t = threading.Thread(target=subnum,args=[])
        t.start()
        threads.append(t)
    
    for i in threads:
        i.join()
    
    print(number)
    

      

    运行结果:

    图片 4

     

    但是你发现没,这个运行太慢了,每个线程都运行了一次sleep,竟然又变成和串行运行差不多了对吧?不过还是和串行稍微有点不同,只是在有同步锁那里是串行,在其他地方还是多线程的效果

     

    那么有朋友要问了,既然都是锁,已经有了一个GIL,那么还要同步锁来干嘛呢?一句话,GIL是着重于保证线程安全,同步锁是用户级的可控机制,开发中防止这种不确定的潜在隐患

     

    三、TransactionScope怎么使用?

     1 try
     2 {
     3     using (TransactionScope scope = new TransactionScope())
     4     {
     5         //TODO:数据处理业务       
     6         scope.Complete();
     7     }
     8 }
     9 catch (Exception ex)
    10 {
    11     throw ex;
    12 }
    

    最基础的容器

    在上面的构想中,Container类有两个函数,一个是Register,一个是Resolve
    容器需要在Register时关联ILogger接口到MyLogger实现,并且需要在Resolve时知道应该为ILogger生成MyLogger的实例。
    以下是实现这两个函数最基础的代码

    public class Container
    {
        // service => implementation
        private IDictionary<Type, Type> TypeMapping { get; set; }
    
        public Container()
        {
            TypeMapping = new Dictionary<Type, Type>();
        }
    
        public void Register<TImplementation, TService>()
            where TImplementation : TService
        {
            TypeMapping[typeof(TService)] = typeof(TImplementation);
        }
    
        public TService Resolve<TService>()
        {
            var implementationType = TypeMapping[typeof(TService)];
            return (TService)Activator.CreateInstance(implementationType);
        }
    }
    

    Container在内部创建了一个服务类型(接口类型)到实现类型的索引,Resolve时使用索引找到实现类型并创建实例。
    这个实现很简单,但是有很多问题,例如

    • 一个服务类型不能对应多个实现类型
    • 没有对实例进行生命周期管理
    • 没有实现构造函数注入

     8.死锁现象/可重用锁

    前面既然已经用了同步锁,那么相信在以后的开发中,绝对会用到使用多个同步锁的时候,所以这里模拟一下使用两个同步锁,看看会有什么现象发生

     

    #!usr/bin/env python
    #-*- coding:utf-8 -*-
    
    # author:yangva
    import threading,time
    
    a = threading.Lock() #创建同步锁对象a
    b = threading.Lock() #创建同步锁对象b
    
    def demo1():
        a.acquire() #加锁
        print('threading model test A....')
        b.acquire()
        time.sleep(0.2)
        print('threading model test B....')
        b.release()
        a.release() #释放
    
    def demo2():
        b.acquire() #加锁
        print('threading model test B....')
        a.acquire()
        time.sleep(0.2)
        print('threading model test A....')
        a.release()
        b.release() #释放
    
    threads = []
    for i in range(5):
        t1 = threading.Thread(target=demo1,args=[])
        t2 = threading.Thread(target=demo2,args=[])
        t1.start()
        t2.start()
        threads.append(t1)
        threads.append(t2)
    
    for i in threads:
        i.join()
    

     

      

    运行结果:

    图片 5

     

    这里就一直阻塞住了,因为demo1函数用的锁是外层a锁,内层b锁,demo2函数刚好相反,外层b锁,内层a锁,所以当多线程运行时,两个函数同时在互抢锁,谁也不让谁,这就导致了阻塞,这个阻塞现象又叫死锁现象。

     

    那么为了避免发生这种事,我们可以使用threading模块下的RLOCK来创建重用锁依此来避免这种现象

     

    #!usr/bin/env python
    #-*- coding:utf-8 -*-
    
    # author:yangva
    import threading,time
    
    r = threading.RLock() #创建重用锁对象
    
    def demo1():
        r.acquire() #加锁
        print('threading model test A....')
        r.acquire()
        time.sleep(0.2)
        print('threading model test B....')
        r.release()
        r.release() #释放
    
    def demo2():
        r.acquire() #加锁
        print('threading model test B....')
        r.acquire()
        time.sleep(0.2)
        print('threading model test A....')
        r.release()
        r.release() #释放
    
    threads = []
    for i in range(5):
        t1 = threading.Thread(target=demo1,args=[])
        t2 = threading.Thread(target=demo2,args=[])
        t1.start()
        t2.start()
        threads.append(t1)
        threads.append(t2)
    
    for i in threads:
        i.join()
    

      

    运行结果:

    图片 6

     

    这个Rlock其实就是Lock+计算器,计算器里的初始值为0,每嵌套一层锁,计算器值加1,每释放一层锁,计算器值减1,和同步锁一样,只有当值为0时才算结束,让其他线程接着抢着运行。而这个Rlock也有一个官方一点的名字,递归锁

     

     那么估计有朋友会问了,为什么会有死锁现象呢?或者你应该问,是什么生产环境导致有死锁现象的,还是那句,为了保护数据同步性,防止多线程操作同一数据时发生冲突。这个说辞很笼统对吧,我说细点。比如前面的购物车系统,虽然我们在操作数据时又重新取了一遍数据来保证数据的真实性,如果多个用户同时登录购物车系统在操作的话,或者不同的操作但会涉及到同一个数据的时候,就会导致数据可能不同步了,那么就可以在内部代码里加一次同步锁,然后再在实际操作处再加一次同步锁,这样就出现多层同步锁,那么也就会出现死锁现象了,而此时这个死锁现象是我们开发中正好需要的。

    我想,说了这个例子你应该可以理解为什么lock里还要有lock,很容易导致死锁现象我们还是要用它了,总之如果需要死锁现象就用同步锁,不需要就换成递归锁。

     

    四、问题探讨

    改进容器的构想 - 类型索引类型

    要让一个服务类型对应多个实现类型,可以把TypeMapping改为

    IDictionary<Type, IList<Type>> TypeMapping { get; set; }
    

    如果另外提供一个保存实例的变量,也能实现生命周期管理,但显得稍微复杂了。
    这里可以转换一下思路,把{服务类型=>实现类型}改为{服务类型=>工厂函数},让生命周期的管理在工厂函数中实现。

    IDictionary<Type, IList<Func<object>>> Factories { get; set; }
    

    有时候我们会想让用户在配置文件中切换实现类型,这时如果把键类型改成服务类型+字符串,实现起来会简单很多。
    Resolve可以这样用: Resolve<Service>(serviceKey: Configuration["ImplementationName"])

    IDictionary<Tuple<Type, string>, IList<Func<object>>> Factories { get; set; }
    

     9.信号量/绑定式信号量

    信号量也是一个线程锁

    1)Semaphore

    信号量感觉更有具有多线程的意义。先不急着说,看看例子就懂:

    #!usr/bin/env python
    #-*- coding:utf-8 -*-
    
    # author:yangva
    import threading,time
    
    s = threading.Semaphore(3) #创建值为3的信号量对象
    
    def demo():
        s.acquire() #加锁
        print('threading model test A....')
        time.sleep(2)
        s.release() #释放
    
    threads = []
    for i in range(10):
        t = threading.Thread(target=demo,args=[])
        t.start()
        threads.append(t)
    
    for i in threads:
        i.join()
    

      

    运行结果:

    图片 7

     

    如果你亲自测试这段代码,你会发现,这个结果是3个一组出的,出了3次3个一组的,最后出了一个一组,3个一组都是并行的,中间停顿2秒。

    这里可以给很形象的例子,假如某个地方的停车位只能同时停3辆车,当停车位有空时其他的车才可以停进来。这里的3个停车位就相当于信号量。

     

    2)BoundedSemaphore

    既然有信号量为我们完成这些一组一组的操作结果,但敢不敢保证这些线程就不会突然的越出这个设定好的车位呢?比如设定好的3个信号量一组,我们都知道线程是争强着运行,万一就有除了设定的3个线程外的一两个线程抢到了运行权,谁也不让谁,就是要一起运行呢?好比,这里只有3个车位,已经停满了,但有人就是要去挤一挤,出现第4辆或者第5辆车的情况,这个和现实生活中的例子简直太贴切了对吧?

    那么我们怎么办?当然这个问题早就有人想好了,所以有了信号量的升级版——绑定式信号量(BoundedSemaphore)。既然是升级版,那么同信号量一样该有的都有的,用法也一样,就是有个功能,在设定好的几个线程一组运行时,如果有其他线程也抢到运行权,那么就会报错

    比如thread_lock = threading.BoundedSemaphore(5),那么多线程同时运行的线程数就必须在5以内(包括5),不然就报错。换句话,它拥有了实时监督的功能,好比停车位上的保安,如果发现车位满了,就禁止放行车辆,直到有空位了再允许车辆进入停车。

    因为这个很简单,就多了个监督功能,其他和semaphore一样的用法,我就不演示了,自己琢磨吧

     

    本文由金沙国际官网发布于编程,转载请注明出处:如何编写一个简单的依赖注入容器,洗礼灵魂

    关键词: