美文网首页PythonPython语言与信息数据获取和机器学习
怎么样才算得上是熟悉多线程编程?

怎么样才算得上是熟悉多线程编程?

作者: 爱秋刀鱼的猫 | 来源:发表于2018-01-23 20:52 被阅读38次

    在知乎上面看见一个很意思的问题,怎么样才算是熟悉多线程编程?
    https://www.zhihu.com/question/22375509 看了很多人的分享,整理一下大家的答案。

    1.明白多进程和多线程的基本概念。

    1. 多进程之间的内存空间是独立的,所以多进程之间多用的通信,而不是加锁(因为变量的内存空间是独立的,要加锁也是“大锁”,比为文件锁这类的)。

    2. 进程之间通信的机制?
      socket,管道,消息队列,共享内存,信号(比如 在terminal 输入 kill -9 就是发送kill信号)

    3. 线程之间的为什么要加锁?如果不加锁会有什么问题?进程为什么不需要加锁?

    #多线程访问共享变量不加锁的情况
    import threading 
    import time 
    
    counter =0 
    def process_item():  # 这段函数的功能很简单,就是给counter 执行10w次的加1操作
        global counter
        for i in range(100000):
            counter=counter + 1
    
    # 创建两个线程,分别执行一次process_item 
    # 按照单线程的思想,如果执行两次process_item 之后,counter的计数应该是20w 
    # but 在多线程的情况下面,会发现counter的值不是那么准确。在大多数情况下它是对的,但有时它会比实际的少几个。
    t1 =  threading.Thread(target=process_item,name=None)
    t2 =  threading.Thread(target=process_item,name=None)
    
    t1.start()
    t2.start()
    t1.join()
    t2.join()
    
    print (counter)  #输出 190654,为什么不是20w ?
    
    """
    原因也很简单,就是修改一个变量不是一个原子操作,实际上分成三步:
    读取--->修改--->写回
    考虑一下这种情况:在当前线程获取到counter值后,另一个线程抢占到了CPU,
    然后同样也获取到了counter值,并进一步将counter值重新计算并完成回写;
    之后时间片重新轮到当前线程(这里仅作标识区分,并非实际当前),
    此时当前线程获取到counter值还是原来的,
    完成后续两步操作后counter的值实际只加上1。
    """
    

    加锁之后的代码:

    import threading 
    import time 
    
    counter =0 
    def process_item():
        global counter
        for i in range(100000):
            lock.acquire()  # 加锁
            counter=counter + 1
            lock.release()  # 释放锁
    
    lock = threading.Lock()
    t1 =  threading.Thread(target=process_item,name=None)
    t2 =  threading.Thread(target=process_item,name=None)
    
    t1.start()
    t2.start()
    t1.join()
    t2.join()
    
    print (counter)  #输出 20w,结果是正常的
    

    2.明白保护线程安全的基本方法有哪些
    进程之间一般讨论的是通信,线程之间讨论的是如何同步,线程互斥和同步的方式有:
    互斥:

    1. 锁(各种类型的加锁) ;

    同步:

    1. 信号量 Semaphores(可以理解是高级的锁,因为内部的实现就是一个带有计数的锁);
    2. 事件 event
    3. 条件 Conditions

    1. 线程加锁的类型
      1.1 mutex 互斥锁
      1.2 递归锁 / python 里面叫做 RLock
      1.3 读写锁
      1.4 自旋锁

    2. 信号量 Semaphores
      可以理解是高级的锁,因为内部的实现就是一个带有计数的锁

    3. event
      基于事件的同步是指:一个线程发送/传递事件,另外的线程等待事件的触发。

    4. condition
      条件同步机制是指:一个线程等待特定条件,而另一个线程发出特定条件满足的信号。

    信号量,事件,条件都是实现线程同步的机制,一些需要线程同步的问题都可以使用几种方式来解决,不同的实现方式可能“麻烦”程度不一样。具体的区别可以看下面的一个练习题:三线程交替打印 ABC 和生产者消费者模型。(我都是使用信号量实现的,使用事件和条件也可以实现,本质是一样的)

    3.明白这些线程安全的方法,包括互斥锁,自旋锁,无锁编程的适用的业务场景是什么?从OS和硬件角度说说原理是怎么样的?开销在哪里?

    在多线程修改一个共享变量的场景下,如果不加锁,很容易出现结果比于其的结果少那么几个数。

    加锁的开销,从os的角度来看就是需要频繁的从用户态进入内核态。
    在申请锁的时候,需要进入内核态申请,如果申请到了,那么返回用户态继续执行;如果没有申请到,那么睡眠在内核态。释放锁的时候,需要再一次进入内核态,然后再返回用户态。

    4.能在现场借助cas操作,风险指针实现无锁的数据结构,比如无锁栈,无锁环形队列。无锁排序链表

    java 里面好像很爱讲 cas操作,但是我不是很懂java。

    c++里面是不是是原子操作std::atomic<int> + validate 关键字 ?
    这个我不是很确定。

    5. 几个题目
    5.1 三个线程轮流执行顺序打印ABC

    使用信号量的机制 来实现 三个线程轮流执行顺序打印ABC:

    # 使用信号量的机制 来实现 三个线程轮流执行顺序打印ABC
    import threading 
    import time 
    
    sema_AtoB = threading.Semaphore(1)
    sema_BtoC = threading.Semaphore(0)
    sema_CtoA = threading.Semaphore(0)
    
    def printA():
        while(True):
            sema_AtoB.acquire()
            print ("A")
            sema_BtoC.release()
    
    def printB():
        while(True):
            sema_BtoC.acquire()
            print ("B")
            sema_CtoA.release()
    
    def printC():
        while(True):
            sema_CtoA.acquire()
            print ("C")
            sema_AtoB.release()
    
    threadlist=[]
    threadlist.append(threading.Thread(target=printA))
    threadlist.append(threading.Thread(target=printB))
    threadlist.append(threading.Thread(target=printC))
    
    for i in threadlist:
        i.start()
    for i in threadlist:
        i.join()
    

    使用信号量实现生产者--消费者模型

    import threading
    import time 
    
    # 生产者的最大容量
    max_capacity = 10
    
    sema = threading.Semaphore(max_capacity)
    
    def consumer():
        times =25
        while(times > 0):
            sema.acquire()
            print("消费了一个,剩下 ",sema._value)
            times -=1
    
    
    def producter():
        times =20
        while(times > 0):
            sema.release()
            print("生产了一个,剩下 ",sema._value)
            times -=1
    
    threadlist=[]
    threadlist.append(threading.Thread(target=consumer))
    threadlist.append(threading.Thread(target=producter))
    
    for i in threadlist:
        i.start()
    
    for i in threadlist:
        i.join()
    
    

    上面这两个例子,使用条件condition 和 事件event 都可以是实现。本质上他们都是线程同步的一种机制。

    5.2 设计一个线程安全的队列
    https://harveyqing.gitbooks.io/python-read-and-write/content/python_basic/fifo_queue.html

    to-do

    设计一个不需要加锁的线程安全的队列
    原子操作 std::atomic<int> + volidation ?


    参考文献 :
    基于python 的锁:
    https://harveyqing.gitbooks.io/python-read-and-write/content/python_advance/python_thread_sync.html

    线程同步的方式:
    http://yoyzhou.github.io/blog/2013/02/28/python-threads-synchronization-locks/

    加锁的类型 :
    http://www.cnblogs.com/SealedLove/archive/2009/02/19/1393755.html

    自旋锁:
    http://www.cnblogs.com/cposture/p/SpinLock.html

    cas无锁编程(compare and set):
    http://blog.csdn.net/aesop_wubo/article/details/7537960

    http://blog.jobbole.com/90811/

    强烈推荐 :
    http://www.dongwm.com/archives/使用Python进行并发编程-线程篇/

    相关文章

      网友评论

        本文标题:怎么样才算得上是熟悉多线程编程?

        本文链接:https://www.haomeiwen.com/subject/xeseaxtx.html