怎么样才算得上是熟悉多线程编程?
在知乎上面看见一个很意思的问题,怎么样才算是熟悉多线程编程?
https://www.zhihu.com/question/22375509 看了很多人的分享,整理一下大家的答案。
1.明白多进程和多线程的基本概念。
-
多进程之间的内存空间是独立的,所以多进程之间多用的通信,而不是加锁(因为变量的内存空间是独立的,要加锁也是“大锁”,比为文件锁这类的)。
-
进程之间通信的机制?
socket,管道,消息队列,共享内存,信号(比如 在terminal 输入 kill -9 就是发送kill信号) -
线程之间的为什么要加锁?如果不加锁会有什么问题?进程为什么不需要加锁?
#多线程访问共享变量不加锁的情况
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.明白保护线程安全的基本方法有哪些
进程之间一般讨论的是通信,线程之间讨论的是如何同步,线程互斥和同步的方式有:
互斥:
- 锁(各种类型的加锁) ;
同步:
- 信号量 Semaphores(可以理解是高级的锁,因为内部的实现就是一个带有计数的锁);
- 事件 event
- 条件 Conditions
-
线程加锁的类型
1.1 mutex 互斥锁
1.2 递归锁 / python 里面叫做 RLock
1.3 读写锁
1.4 自旋锁 -
信号量 Semaphores
可以理解是高级的锁,因为内部的实现就是一个带有计数的锁 -
event
基于事件的同步是指:一个线程发送/传递事件,另外的线程等待事件的触发。 -
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进行并发编程-线程篇/