大师兄的Python学习笔记(十): 多进程和多线程

大师兄的Python学习笔记(九): logging模块与日志
大师兄的Python学习笔记(十一): 时间模块time,datetime和calendar

一、关于多进程和多线程

  • 多进程和多线程是让系统同时运行多个任务的做法。
  • 使用多进程/多线程可以充分利用闲置的系统资源。
  • 使用多进程/多线程可以避免由于阻塞造成的程序停滞(尤其是网络请求)。
  • 无论进程或线程,都不是越多越好,就像在厨房做饭也不是人越多越快一个道理。
1. 进程和线程
1.1进程
  • 进程是程序运行的过程。
  • 是操作系统进行资源分配和调度的一个独立单位。
  • 是应用程序运行的载体。
  • 进程包含地址空间,内存,数据栈等。
  • 比如在系统中运行一个程序,就是一个进程。
  • 一个进程是由一个或多个线程组成的。
1.2 线程
  • 线程是程序执行中的执行片段。
  • 是程序执行流的最小单元。
  • 是处理器调度和分派的基本单位。
  • 一个进程至少包含一个线程,也可以包含多个线程。
1.3 多进程
  • 每个进程都有独立的运行环境。
  • 由于进程的独立性高,所以多进程相对稳定。
  • 相应的,多进程消耗大量系统资源。
  • 多个进程间的数据传输很麻烦。
1.4 多线程
  • 一个进程中的多个线程可以共享数据和上下文运行环境。
  • 多线程消耗的资源相对少,速度也快一些。
  • 多线程的稳定性差,可能某个子线程崩溃,整个进程会被迫停止。
  • 多线程间的信息传输相对容易。
  • 现在主流使用多线程,多进程使用的少。
1.5 全局解释器锁(GIL)
  • GIL存在于CPython,也就是我们常用的Python版本。
  • python代码的执行由Python虚拟机控制, 在任意时刻只有一个线程在解释器中运行。
  • 具体的情况大概是GIL控制虚拟机的访问控制,在同一时刻只给一个线程发锁。
  • 这样的后果是导致CPython不能利用物理多核的性能加速运行。
  • 随着版本更新,未来有可能解决GIL的问题。
  • 目前也有一些变通的办法,比如使用线程池,ctype等。

二、Python中的多进程

  • 主要有os.forkmultiprocessingsubprocess等方法。
1. os.fork

1)关于os.fork

  • 可以在UNIX/LINUX系统下使用。
  • 用于创建多进程。

2)os.fork的使用

  • 在调用os.fork时,会返回两次,分别是当前进程(父进程)和当前进程的复制(子进程)。
  • 用os.getpid()可以获得当前进程的进程id。
  • 用os.getppid()可以获得当前进程父进程进程的进程id。
# fork1.py
>>>import os
>>>def child():
>>>  print('this is child:',os.getpid())
>>>  print('my parent is:',os.getppid())
>>>def parent():
>>>  newpid = os.fork()
>>>  if newpid ==0:
>>>    child()
>>>  else:
>>>    print('this is parent:',os.getpid(),newpid)
>>>parent()

# linux控制台
>>>python fork1.py
this is parent: 21665 21666
this is child: 21666
my parent is: 21665
2. multiprocessing包

1)关于multiprocessing

  • 用于创建多线程。
  • multiprocessing在python3中是标准库内容。
  • 可以在Windows系统、Unix/Linux系统跨平台使用。
  • 接口和线程相似。

2)multiprocessing.Process方法

  • 用Process类来创建进程对象。
  • 每个对象都是一个新的进程。
# test_Process.py
>>>from multiprocessing import Process
>>>import time

>>>def Dog():
>>>    for i in range(1,5):
>>>        print('汪汪!')
>>>        time.sleep(1)

>>>def Cat():
>>>    for i in range(1,5):
>>>        print('喵喵~')
>>>        time.sleep(1)

>>>p1 = Process(target=Dog) # 创建进程1,调用Dog函数
>>>p2 = Process(target=Cat) # 创建进程2, 调用Cat函数
>>>p1.start() # 开始进程1
>>>p2.start() # 开始进程2

# console
>>>ubuntu@VM-0-4-ubuntu:~$ python test_process.py # 这里的运行顺序不是固定的
喵喵~
汪汪!
喵喵~
汪汪!
汪汪!
喵喵~
喵喵~
汪汪!

3)multiprocessing.Process的run()方法

  • 使用了Process类的继承方式。
  • 当没有个Process指定target时,会自动运行类的run()方法。
  • 这里本质和2)是相同的
# test_process2.py
>>>from multiprocessing import Process
>>>import time

>>>class Process_Dog(Process):
>>>    def __init__(self):
>>>        super().__init__()

>>>    def run(self):
>>>        for i in range(5):
>>>            print('汪汪!')
>>>            time.sleep(1)

>>>class Process_Cat(Process):
>>>    def __init__(self):
>>>        super().__init__()

>>>    def run(self):
>>>        for i in range(1, 5):
>>>            print('喵喵~')
>>>            time.sleep(1)

>>>p_dog = Process_Dog()
>>>p_cat = Process_Cat()
>>>p_dog.start()
>>>p_cat.start()

# console
>>>ubuntu@VM-0-4-ubuntu:~$ python test_process2.py
喵喵~
汪汪!
汪汪!
喵喵~
喵喵~
汪汪!
汪汪!
喵喵~
汪汪!

4)multiprocessing.Pool进程池

  • 在使用大量进程时可以使用进程池Pool。
  • 进程池开启的个数默认是CPU的个数。
  • 可以使用apply()或apply_async()(阻塞和非阻塞)的方式向线程池中添加Process。
  • 可以用join()方法启动线程池中的所有线程,并等待执行完毕。
  • 调用join()之前需要先调用close()方法。
  • 调用close()之后就不能继续添加新的Process。
#test_pool.py
>>>from multiprocessing import Pool
>>>import time

>>>def Dog():
>>>    for i in range(1,5):
>>>        print('汪汪!')
>>>        time.sleep(1)

>>>def Cat():
>>>    for i in range(1,5):
>>>        print('喵喵~')
>>>        time.sleep(1)

>>>pool = Pool(2) # 定义线程池大小
>>>pool.apply_async(Dog) # 以非阻塞模式加入线程池
>>>pool.apply_async(Cat)
>>>pool.close() # 关闭线程池,准备调用
>>>pool.join() # 主进程阻塞,等待子进程退出

# console
>>>ubuntu@VM-0-4-ubuntu:~$ python test_pool.py # 这里的结果是固定的
汪汪!
喵喵~
汪汪!
喵喵~
汪汪!
喵喵~
汪汪!
喵喵~

5)多进程和进程池的区别
假设餐馆有20个订单,只有3个锅炒菜:

  • 多进程:20个厨子(进程)抢锅,每个厨子只炒1分钟自己的菜,到时间就换人。等于20个进程做20个任务。
  • 进程池:3个锅(进程池包含3个进程)放在火眼上,哪个厨子(进程)来就继续炒已经在锅里的菜。等于3个进程做20个任务。
3. subprocess包

1)关于subprocess

  • 用于启动一个外部进程。
  • subprocess在python3中是标准库内容。
  • 可以在Windows系统、Unix/Linux系统跨平台使用。

2)subprocess.call(args,*,stdin=None,stdout=None,stderr=None,shell=False,timeout=None)

  • 用于执行shell指令。
  • args表示提供的指令列表。
  • stdin表示输入方式。
  • stdout表示输出方式。
  • stderr表示输出错误的方式。
  • shell表示是否和shell直接传字符串。
  • timeout表示连接超时时间。
#test_subprocess.py
>>>print('here in subprocess!')

#python console
>>> import subprocess
>>> subprocess.call(['python','test_subprocess.py'])    
here in subprocess!
0

3)subprocess.Popen(args, bufsize=-1, executable=None, stdin=None, stdout=None, stderr=None, preexec_fn=None, close_fds=True, shell=False, cwd=None, env=None, universal_newlines=False, startupinfo=None, creationflags=0, restore_signals=True, start_new_session=False, pass_fds=())

  • 用于创建外部进程并处理复杂的交互。
>>>subprocess.Popen(['python','test_subprocess.py'],shell=True)
<subprocess.Popen object at 0x7f58c9ba19d0>
Python 3.7.5 (default, Oct 25 2019, 15:51:11) 
[GCC 7.3.0] :: Anaconda, Inc. on linux
Type "help", "copyright", "credits" or "license" for more information.

4) subprocess.getoutput(cmd)

  • 返回在shell中执行cmd的输出。
>>> subprocess.getoutput('ps') 
'  PID TTY          TIME CMD\n13239 pts/0    00:00:00 bash\n13284 pts/0    00:00:00 python\n13411 pts/0    00:00:00 sh\n13412 pts/0    00:00:00 ps'

5) subprocess.communicate(input=None, timeout=None)

  • 实现子进程的输入。
#input.py
>>>age = input('please input your age:') #这里会要求在console输入
>>>print('you are {age} years old.'.format(age=age))

#subprocess_input.py
>>>import subprocess
>>>p = subprocess.Popen(['python','input.py'],stdin=subprocess.PIPE,stdout=subprocess.PIPE,stderr=subprocess.PIPE) # 建立进程的同时,将输入输出都绑定到subprocess的管道上
>>>output,err = p.communicate(b'19') # 这里返回input的输出,字节流需要用byte输入。
>>>print(output.decode('utf-8')) # 解码byte流

#console
>>>ubuntu@VM-0-4-ubuntu:~$ python subprocess_input.py
please input your age:you are 19 years old.

6)subprocess的更多方法。

4. 进程间的通讯
  • 由于进程在内存中是独立的,所以通讯机制要比线程复杂很多。
  • 主要的通讯方式包含匿名管道具名管道套接字信号四种方式。
4.1 关于管道
  • 一个程序在管道一端写入数据,而另一个程序在另一端读取数据。
  • 管道有先进先出(FIFO)的特点。
  • 管道是单向的,类似共享的内存缓冲。
  • 对于两端的程序接口来说,类似简单的文件。
  • 管道偏向在操作系统内部使用。
  • 管道分为匿名管道和具名管道两种。
4.2 匿名管道
  • 匿名管道用os.pipe()方法创建。
  • 匿名管道允许共享文件描述符的线程及进程传递数据。
  • 匿名管道仅在进程内部存在。
  • 仅可以在Unix平台下使用。
# pipe1.py
>>>import os,time
>>>def child(pipeout):
>>>  count=0
>>>  while True:
>>>    time.sleep(count) # 让父进程等待
>>>    msg = ('child count {count}'.format(count=count)).encode() # 发送到pipe的内容需要时二进制
>>>    os.write(pipeout,msg) # 管道的输入端
>>>    count = (count+1)%5 # 最后四位+1

>>>def parent():
>>>  pipein,pipeout = os.pipe()  # 创建一个管道
>>>  if os.fork() == 0: # 创建线程
>>>    child(pipeout) # 在子进程中运行child
>>>  else:
>>>    while True: # 在父进程中监听管道
>>>      line = os.read(pipein,32) 
>>>     print('parent {pid} got {line} at {time}'.format(pid=os.getpid(),line=line,time=time.time()))

parent()

# console
>>>ubuntu@VM-0-4-ubuntu:~$ python pip1.py
parent 28186 got b'child count 0' at 1578817490.278132
parent 28186 got b'child count 1' at 1578817491.2795548
parent 28186 got b'child count 2' at 1578817493.2802281
parent 28186 got b'child count 3' at 1578817496.2834265
parent 28186 got b'child count 4' at 1578817500.283922
parent 28186 got b'child count 0' at 1578817500.284008
parent 28186 got b'child count 1' at 1578817501.2851105
parent 28186 got b'child count 2' at 1578817503.2853155
parent 28186 got b'child count 3' at 1578817506.286127
parent 28186 got b'child count 4' at 1578817510.2898583
parent 28186 got b'child count 0' at 1578817510.2899537
parent 28186 got b'child count 1' at 1578817511.2903323
parent 28186 got b'child count 2' at 1578817513.2906644
... ...
4.3 具名管道
  • 具名管道用os.mkFIFO()方法创建。
  • 具名管道与匿名管道的区别是,他与系统中一个真实的文件相关联,而不依赖任何共享的内存。
  • 所以它可以长时间存在。
  • 对于程序来讲,它是外部文件。
# pipe2.py
>>>import os,time,sys
>>>fifoname='/tmp/pipefifo'

>>>def child():
>>>  count = 0
>>>  pipeout = os.open(fifoname,os.O_NONBLOCK|os.O_WRONLY)
>>>  while True:
>>>    time.sleep(count)
>>>    msg = ('child count {count}\n'.format(count=count)).encode() # 这里必须有\n
>>>    os.write(pipeout,msg)  # 写入pipe
>>>    count = (count +1)%5

>>>def parent():
>>>  pipein = open(fifoname,'r') # 从pipe读取
>>>  while True:
>>>    line = pipein.readline()[:-1]
>>>    print('parent {} got {} at {}'.format(os.getpid(),line,time.time()))

>>>if __name__ == '__main__':
>>>  if not os.path.exists(fifoname):
>>>    os.mkfifo(fifoname)
>>>  if len(sys.argv) == 1:  # 由于本来就是独立的进程,所以不需要再分开进程。
>>>    parent()
>>>  else:
>>>    child()

# console 1
>>>python pipe2.py
parent 9847 got b'child count 1' at 1578823592.4824383
parent 9847 got b'child count 2' at 1578823592.4824595
parent 9847 got b'child count 3' at 1578823592.4824812
... ...

# console 2
>>>python pipe2.py -child
4.4 套接字
  • 套接字由socket模块实现。
  • 可以让数据传输在同一台机器上的不同程序间进行,也可以在远程联网的程序间进行。
  • 套接字根据端口号进行识别,而非文件系统的路径名称。
  • 可以传输任意Python对象,比如列表、字典和pickle对象等。
  • 可以在所有平台上运行。
# socket_test.py
>>>from socket import socket,AF_INET,SOCK_STREAM
>>>import time

>>>port = 10001
>>>host = 'localhost'

>>>def server():
>>>    sock = socket(AF_INET,SOCK_STREAM) # ip地址
>>>    sock.bind(('',port))  # 绑定的端口
>>>    sock.listen(5) # 允许5个客户端接入
>>>    while True: 
>>>        connection,address = sock.accept() # 开始监听
>>>        data = connection.recv(1024) # 获取数据
>>>        reply = 'server got: {}'.format(data) # 返回的数据
>>>        connection.send(reply.encode()) # 发送返回的数据

>>>def client(name):
>>>    time.sleep(1)
>>>    sock = socket(AF_INET,SOCK_STREAM)
>>>    sock.connect((host,port))
>>>    sock.send(name.encode())
>>>    reply = sock.recv(1024)
>>>    sock.close()
>>>    print('client got: {}'.format(reply))

>>>if __name__ == '__main__':
>>>    from multiprocessing import Process
>>>    p_server = Process(target=server) # server 进程
>>>    p_server.start()
>>>    for i in range(5):
>>>        p_client = Process(target=client,args=('client'+str(i),)) # 5个client进程
>>>        p_client.start()
>>>        p_client.join()

#console
>>>python socket_test.py
client got: b"server got: b'client0'"
client got: b"server got: b'client1'"
client got: b"server got: b'client2'"
client got: b"server got: b'client3'"
client got: b"server got: b'client4'"
4.5 信号
  • Python中可以使用signal模块发送信号。
  • 只能提供一个基于事件的通信机制,不如其它几种方式强大。
  • 信号根据编号来识别,而不是堆栈。
  • 机制类似跨软件的异常。
  • 作用域其实在Python解释器之外,依赖操作系统。
  • 在Unix和Windows中都可以使用。
# testSignal.py
>>> import signal,time
>>> def onSignal(signum,stackframe):
>>>     print('qq is waiting for dinner',signum,'at',time.asctime())

>>> while True:
>>>     print('Setting at',time.asctime())
>>>     signal.signal(signal.SIGALRM,onSignal)
>>>     signal.alarm(5)
>>>     signal.pause() 

# console 
>>>python testSignal.py
Setting at Mon Jan 13 22:40:54 2020
qq is waiting for dinner 14 at Mon Jan 13 22:40:59 2020
Setting at Mon Jan 13 22:41:00 2020
qq is waiting for dinner 14 at Mon Jan 13 22:41:05 2020
Setting at Mon Jan 13 22:41:00 2020
qq is waiting for dinner 14 at Mon Jan 13 22:41:05 2020
Setting at Mon Jan 13 22:41:00 2020
qq is waiting for dinner 14 at Mon Jan 13 22:41:05 2020
4.6 multiprocessor.queue()方法
  • multiprocessing模块封装了底层的通信机制,用起来会更容易。
  • multiprocessor.queue()创建了一个先进先出(FIFO)的队列。
  • 用put()方法塞入数据,用get()方法获取(pop)数据。
#queue.py
>>>from multiprocessing import Process,Queue
>>>import os,time
queue = Queue() # 创建一个Queue
>>>def child(queue): 
>>>    count = 0
>>>    while True:
>>>        time.sleep(count)
>>>        msg = ('child count {count}'.format(count=count)) 
>>>        queue.put(msg) #  塞入数据
>>>        count += 1

>>>def parent(queue):
>>>    while True:
>>>        line = queue.get() # 获取数据
>>>        print('parent got {} at {}'.format(line, time.time()))

>>>if __name__ == '__main__':
>>>    pp = Process(target=parent,args=(queue,)) # args如果只有一个参数,需要在参数后加",",为了证明他是tuple
>>>    pc = Process(target=child,args=(queue,))
>>>    pp.start()
>>>    pc.start()
>>>    pp.join() # 等待pp的结果

# console
>>>python queue.py
parent got child count 0 at 1578907261.1464262
parent got child count 1 at 1578907262.146753
parent got child count 2 at 1578907264.147026
parent got child count 3 at 1578907267.1476533
parent got child count 4 at 1578907271.1483703

三、Python中的多线程

  • Python标准库有两个线程模块,分别是_threadthreading
1. _thread模块

1)关于_thread模块

  • _thread模块比threading要简单。
  • 接口层面也相对较低。
  • 可以在所有平台使用。
  • 这个模块不常用。

2)使用_thread模块

  • 使用_thread.start_new_thread(function,args[,kwargs])开启新线程。
  • function - 线程函数。
  • args - 传递给线程函数的参数,他必须是个tuple类型。
  • kwargs - 可选参数。
>>>import _thread as thread,time

>>>def counter(id,count):
>>>    for i in range(count):
>>>        time.sleep(1)
>>>        print('pupply {} is waiting for food,{}st time.'.format(id,i)) 
       
>>>for i in range(3):
>>>    thread.start_new_thread(counter,(i,5)) # 创建并启动3个不同的线程
>>>    time.sleep(5)
>>>    print('food time now!')
pupply 0 is waiting for food,0st time.
pupply 0 is waiting for food,1st time.
pupply 0 is waiting for food,2st time.
pupply 0 is waiting for food,3st time.
food time now!
pupply 0 is waiting for food,4st time.
pupply 1 is waiting for food,0st time.
pupply 1 is waiting for food,1st time.
pupply 1 is waiting for food,2st time.
pupply 1 is waiting for food,3st time.
food time now!
pupply 1 is waiting for food,4st time.
pupply 2 is waiting for food,0st time.
pupply 2 is waiting for food,1st time.
pupply 2 is waiting for food,2st time.
pupply 2 is waiting for food,3st time.
food time now!
pupply 2 is waiting for food,4st time.
2. threading模块

1)关于threading模块

  • threading模块封装了_thread模块。
  • 接口基于面相和类等较高层面的接口。
  • 是多线程的常用模块。

2)使用threading模块

  • 使用threading.thread(target=function_name, args=(function_parameter1, function_parameterN))开启新线程。
  • 使用threading.start()启动线程。
  • function对应函数名。
  • target对应参数(tuple类型)。
>>>import threading

>>>def counter(id,count):
>>>    for i in range(count):
>>>        time.sleep(1)
>>>        print('pupply {} is waiting for food,{}st time.'.format(id,i))
       
>>>for i in range(3):
>>>    t = threading.Thread(target=counter,args=(i,5)) # 创建线程
>>>    t.start() # 开启线程
>>>    time.sleep(5)
>>>    print('food time now!')
pupply 0 is waiting for food,0st time.
pupply 0 is waiting for food,1st time.
pupply 0 is waiting for food,2st time.
pupply 0 is waiting for food,3st time.
food time now!
pupply 0 is waiting for food,4st time.
pupply 1 is waiting for food,0st time.
pupply 1 is waiting for food,1st time.
pupply 1 is waiting for food,2st time.
pupply 1 is waiting for food,3st time.
food time now!
pupply 1 is waiting for food,4st time.
pupply 2 is waiting for food,0st time.
pupply 2 is waiting for food,1st time.
pupply 2 is waiting for food,2st time.
pupply 2 is waiting for food,3st time.
food time now!
pupply 2 is waiting for food,4st time.

3)使用类的继承方式

  • start()方法实际上是把需要并行处理的代码放在run()方法中,并自动调用 run()方法。
  • run()方法可以被子类继承和重写。
>>>import threading,time
>>>class Mythread(threading.Thread):
>>>    def __init__(self,id,count,mutex):
>>>        self.id = id
>>>        self.count = count
>>>        self.mutex = mutex # 为了保证输出同步加了线程锁
>>>        threading.Thread.__init__(self)
   
>>>    def run(self): # 重写run方法
>>>        for i in range(self.count):
>>>            time.sleep(1)
>>>            with self.mutex:
>>>                print('pupply {} is waiting for food,{}st time.'.format(self.id,i))

>>>stdoutmutex = threading.Lock() # 创建线程锁
>>>threads = []
>>>for i in range(3):
>>>    t = Mythread(i,5,stdoutmutex)
>>>    threads.append(t)

>>>for t in threads:
>>>    t.start()
>>>print('food time now!')
food time now!
pupply 1 is waiting for food,0st time.
pupply 0 is waiting for food,0st time.
pupply 2 is waiting for food,0st time.
pupply 1 is waiting for food,1st time.
pupply 0 is waiting for food,1st time.
pupply 2 is waiting for food,1st time.
pupply 1 is waiting for food,2st time.
pupply 0 is waiting for food,2st time.
pupply 2 is waiting for food,2st time.
pupply 1 is waiting for food,3st time.
pupply 0 is waiting for food,3st time.
pupply 2 is waiting for food,3st time.
pupply 1 is waiting for food,4st time.
pupply 0 is waiting for food,4st time.
pupply 2 is waiting for food,4st time.

4)关于线程安全

  • 当多个线程同时访问一个变量时(共享变量),会产生线程安全问题。
  • 需要用锁(Lock)或信号量(Semaphore)来解决线程安全问题。
  • 有一些变量类型是不会发生线程安全问题的,比如queue类型。
>>>from threading import Thread
>>>import time
>>>n = 0
>>>def count(): # 每次计算的结果应该都为0
>>>    global n
>>>    n = n + 1 
>>>    n = n - 1
>>>def run_thread():
>>>    for i in range(10000000): #  大量的计算时产生产生冲突的几率更大
>>>        count()
>>>t1 = Thread(target=run_thread,args=())
>>>t2 = Thread(target=run_thread,args=())
>>>t1.start()
>>>t2.start()
>>>t1.join()
>>>t2.join()
>>>print(n)  # 由于线程间的计算冲突,这里算出了错误的结果
12

5)线程锁Lock()

  • 线程锁相当于给变量上了一把锁,每次只给一个线程钥匙。
  • threading.Lock()创建锁。
  • lock.acquire()交给线程钥匙。
  • lock.release()将锁还给系统。
>>>from threading import Thread,Lock
>>>import time
>>>lock = Lock() # 创建锁,由于我用from的方式引用,所以这里可以直接使用函数名。
>>>n = 0
>>>def count():
>>>    global n
>>>    n = n + 1 
>>>    n = n - 1
>>>def run_thread():
>>>    for i in range(10000000):
>>>        lock.acquire() # 将钥匙交给线程
>>>        try:  # 如果访问上锁的变量会返回异常,所以这里用需要用到异常处理。
>>>            count()
>>>        finally:
>>>            lock.release() # 结束后一定要将锁还给系统,否则会造成死锁。
>>>t1 = Thread(target=run_thread,args=())
>>>t2 = Thread(target=run_thread,args=())
>>>t1.start()
>>>t2.start()
>>>t1.join()
>>>t2.join()
>>>print(n) # 这次结果就没错了
0

6)信号量Semaphore()

  • 信号量Semaphore是锁Lock的进化版本,可以同时给多个线程钥匙。
  • 意味着可以允许固定数量的线程访问。
  • threading.Semaphore(n)创建锁,n表示钥匙的数量。
  • Semaphore.acquire()交给线程钥匙。
  • Semaphore.release()将锁还给系统。
>>>from threading import Thread,Semaphore
>>>import time
>>>sem = Semaphore(3) # 同时允许3个线程访问
>>>n = 0
>>>def count():
>>>    global n
>>>    n = n + 1 
>>>    n = n - 1
>>>def run_thread():
>>>    for i in range(1000000):
>>>        sem.acquire(5)
>>>        try:
>>>            count()
>>>        finally:
>>>            sem.release()
>>>for i in range(5):
>>>    t = Thread(target=run_thread,args=())
>>>    t.start()
>>>    t.join()
>>>print(n)
0

参考资料



本文作者:大师兄(superkmi)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容

  • 多进程 要让python程序实现多进程,我们先了解操作系统的相关知识。 Unix、Linux操作系统提供了一个fo...
    蓓蓓的万能男友阅读 590评论 0 1
  • 线程 操作系统线程理论 线程概念的引入背景 进程 之前我们已经了解了操作系统中进程的概念,程序并不能单独运行,只有...
    go以恒阅读 1,630评论 0 6
  • 在讨论多线程之前,让我们先讨论线程。线程是进程中轻量级的最小部分,可以与同一进程的其他部分(其他线程)并发运行。线...
    墨雨轩夏阅读 190评论 0 0
  • 水还是有些凉,我洗了萝卜,直接没擦手就上床了,啃着萝卜,瞥见手上的水滴各不相同,这也许就是大自然的智慧,一切各不相...
    柔弱的顶级宠妇珐阅读 164评论 0 0
  • 本篇主要是对小码哥底层视频学习的总结。方便日后复习。 本篇学习总结: NSObject对象/自定义类的对象/继承关...
    329fd8af610c阅读 356评论 0 0