PythonPython语言与信息数据获取和机器学习

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

2018-01-23  本文已影响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进行并发编程-线程篇/

上一篇下一篇

猜你喜欢

热点阅读