线程

一、线程理论

进程像是一个正在运行着的工厂,而线程是其中运作着的流水线。线程才是CPU上真正的执行单位

 

线程和进程的区别

1.线程共享资源

2.创建线程所消耗的资源小于创建进程,比如时间。

 

二、threading模块

threading和multiprocess两个模块在使用层面上有很大的相似性,会有很多类似的功能。

1.开启的两种方式与进程一致

方式一
from threading import Thread
import time

def sayhi(name):
   time.sleep(2)
   print('%s say hello' %name)

if __name__ == '__main__':
   t=Thread(target=sayhi,args=('egon',))
   t.start()
   print('主线程')

 

2.多线程和多进程

1.开启速度上

多线程要快于多进程

 

2.pid

多线程的pid是一样的

多进程的pid每个都不一样

 

3.数据

多线程共享数据

多进程之间隔离数据

 

3.Thread对象的其他属性和方法

 

Thread实例对象的方法
 # isAlive(): 返回线程是否活动的。
 # getName(): 返回线程名。
 # setName(): 设置线程名。

threading模块提供的一些方法:
 # threading.currentThread(): 返回当前的线程变量。
 # threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。
 # threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。

 

4.守护线程

与守护进程一样,随着主线程运行完毕后被销毁

 

运行完毕并非终止运行

对于主进程来说,运行完毕指的是主进程代码运行完毕

对于主线程来说,运行完毕指的是主线程所在的进程内所有非守护进程运行完毕,主线程才算是运行完毕

验证

from threading import Thread
import time
def sayhi(name):
   time.sleep(2)
   print('%s say hello' %name)

if __name__ == '__main__':
   t=Thread(target=sayhi,args=('egon',))
   t.setDaemon(True) #必须在t.start()之前设置
   t.start()

   print('主线程')
   print(t.is_alive())

运行结果

主线程
True

 

5.GIL全局解释所

GIL本质上就是互斥锁,但是二者的区别在于:前者使用在解释器上的锁,用于垃圾回收等代码;后者适用于应用程序中的锁,保证自己应用程序的质量。

 

多核多用于计算

多线程多用于I/O操作比较多的情况下

 

6.死锁与递归锁

死锁的概念: 是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程,如下就是死锁

from threading import Thread,Lock
import time
mutexA=Lock()
mutexB=Lock()

class MyThread(Thread):
   def run(self):
       self.func1()
       self.func2()
   def func1(self):
       mutexA.acquire()
       print('\033[41m%s 拿到A锁\033[0m' %self.name)

       mutexB.acquire()
       print('\033[42m%s 拿到B锁\033[0m' %self.name)
       mutexB.release()

       mutexA.release()

   def func2(self):
       mutexB.acquire()
       print('\033[43m%s 拿到B锁\033[0m' %self.name)
       time.sleep(2)

       mutexA.acquire()
       print('\033[44m%s 拿到A锁\033[0m' %self.name)
       mutexA.release()

       mutexB.release()

if __name__ == '__main__':
   for i in range(10):
       t=MyThread()
       t.start()

运行结果

Thread-1 拿到A锁
Thread-1 拿到B锁
Thread-1 拿到B锁
Thread-2 拿到A锁 #出现死锁,整个程序阻塞住

 

递归锁的概念:在Python中为了支持在同一线程中多次请求同一资源,python提供了可重入锁RLock。

这个RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次require。直到一个线程所有的acquire都被release,其他的线程才能获得资源。上面的例子如果使用RLock代替Lock,则不会发生死锁,二者的区别是:递归锁可以连续acquire多次,而互斥锁只能acquire一次

from threading import Thread,RLock
import time

mutexA=mutexB=RLock() #一个线程拿到锁,counter加1,该线程内又碰到加锁的情况,则counter继续加1,这期间所有其他线程都只能等待,等待该线程释放所有锁,即counter递减到0为止

class MyThread(Thread):
   def run(self):
       self.func1()
       self.func2()
   def func1(self):
       mutexA.acquire()
       print('\033[41m%s 拿到A锁\033[0m' %self.name)

       mutexB.acquire()
       print('\033[42m%s 拿到B锁\033[0m' %self.name)
       mutexB.release()

       mutexA.release()

   def func2(self):
       mutexB.acquire()
       print('\033[43m%s 拿到B锁\033[0m' %self.name)
       time.sleep(2)

       mutexA.acquire()
       print('\033[44m%s 拿到A锁\033[0m' %self.name)
       mutexA.release()

       mutexB.release()

if __name__ == '__main__':
   for i in range(10):
       t=MyThread()
       t.start()

运行结果

Thread-1 拿到A锁
Thread-1 拿到B锁
Thread-1 拿到B锁
Thread-1 拿到A锁
Thread-2 拿到A锁
Thread-2 拿到B锁
Thread-2 拿到B锁
Thread-2 拿到A锁
Thread-4 拿到A锁
Thread-4 拿到B锁
Thread-4 拿到B锁
Thread-4 拿到A锁
Thread-6 拿到A锁
Thread-6 拿到B锁
Thread-6 拿到B锁
Thread-6 拿到A锁
Thread-8 拿到A锁
Thread-8 拿到B锁
Thread-8 拿到B锁
Thread-8 拿到A锁
Thread-10 拿到A锁
Thread-10 拿到B锁
Thread-10 拿到B锁
Thread-10 拿到A锁
Thread-5 拿到A锁
Thread-5 拿到B锁
Thread-5 拿到B锁
Thread-5 拿到A锁
Thread-9 拿到A锁
Thread-9 拿到B锁
Thread-9 拿到B锁
Thread-9 拿到A锁
Thread-7 拿到A锁
Thread-7 拿到B锁
Thread-7 拿到B锁
Thread-7 拿到A锁
Thread-3 拿到A锁
Thread-3 拿到B锁
Thread-3 拿到B锁
Thread-3 拿到A锁

Process finished with exit code 0

 

7.信号量

信号量也是一把锁,但不同于互斥锁的只有一个进程能够强到锁。信号量可以直接个数,然后一群线程去抢夺制定数量的锁去运行。

from threading import Thread,Semaphore
import threading
import time

def func():
   sm.acquire()
   print('%s get sm' %threading.current_thread().getName())
   time.sleep(3)
   sm.release()

if __name__ == '__main__':
   sm=Semaphore(5)
   for i in range(23):
       t=Thread(target=func)
       t.start()

 

8.event

人为设置一个线程的阻塞点,这个阻塞点根据传过来的信号继续或是继续等待。

 

from threading import Event

event.isSet():返回event的状态值;#默认是false

event.wait():如果 event.isSet()==False将阻塞线程;

event.set()设置event的状态值为True,所有阻塞池的线程激活进入就绪状态, 等待操作系统调度;

event.clear():恢复event的状态值为False。

 

from threading import Thread,Event
import threading
import time,random
def conn_mysql():
   count=1
   while not event.is_set():
       if count > 3:
           raise TimeoutError('链接超时')
       print('<%s>第%s次尝试链接' % (threading.current_thread().getName(), count))
       event.wait(0.5)
       count+=1
   print('<%s>链接成功' %threading.current_thread().getName())


def check_mysql():
   print('\033[45m[%s]正在检查mysql\033[0m' % threading.current_thread().getName())
   time.sleep(random.randint(2,4))
   event.set()
if __name__ == '__main__':
   event=Event()
   conn1=Thread(target=conn_mysql)
   conn2=Thread(target=conn_mysql)
   check=Thread(target=check_mysql)

   conn1.start()
   conn2.start()
   check.start()

 

9.计时器

指定n秒后执行某操作

from threading import Timer

def hello():
   print("hello, world")

t = Timer(1, hello)
t.start()  # after 1 seconds, "hello, world" will be printed

 

10.线程队列

当需要信息能够安全的在几个线程之间传输的时候,就需要用到线程队列。

有三种不用的用法

**先进先出**

import queue

q=queue.Queue()
q.put('first')
q.put('second')
q.put('third')

print(q.get())
print(q.get())
print(q.get())



'''
结果(先进先出):
first
second
third
'''

 

后进先出#堆栈

import queue

q=queue.LifoQueue()  # last in first
q.put('first')
q.put('second')
q.put('third')

print(q.get())
print(q.get())
print(q.get())



'''
结果(后进先出):
third
second
first
'''

 

优先级队列

import queue

q=queue.PriorityQueue()
#put进入一个元组,元组的第一个元素是优先级(通常是数字,也可以是非数字之间的比较),数字越小优先级越高
q.put((20,'a'))
q.put((10,'b'))
q.put((30,'c'))

print(q.get())
print(q.get())
print(q.get())



'''
结果(数字越小优先级越高,优先级高的优先出队):
(10, 'b')
(20, 'a')
(30, 'c')
'''

 

三、进程池和线程池

本质上就是对进程和线程加以限制开启数量

concurrent.futures模块提供了高度封装的异步调用接口
ThreadPoolExecutor:线程池,提供异步调用
ProcessPoolExecutor: 进程池,提供异步调用

 

1.进程池

基本方法

1、submit(fn, *args, **kwargs)
异步提交任务

2、map(func, *iterables, timeout=None, chunksize=1)
取代for循环submit的操作

3、shutdown(wait=True)
相当于进程池的pool.close()+pool.join()操作
wait=True,等待池内所有任务执行完毕回收完资源后才继续
wait=False,立即返回,并不会等待池内的任务执行完毕
但不管wait参数为何值,整个程序都会等到所有任务执行完毕
submit和map必须在shutdown之前

4、result(timeout=None)
取得结果

5、add_done_callback(fn)
回调函数

 

用法

from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor

import os,time,random
def task(n):
   print('%s is runing' %os.getpid())
   time.sleep(random.randint(1,3))
   return n**2

if __name__ == '__main__':

   executor=ProcessPoolExecutor(max_workers=3)

   futures=[]
   for i in range(11):
       future=executor.submit(task,i)
       futures.append(future)
   executor.shutdown(True)
   print('+++>')
   for future in futures:
       print(future.result())

 

2.线程池

用法

把ProcessPoolExecutor换成ThreadPoolExecutor,其余用法全部相同

 

3.map

 

from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor

import os,time,random
def task(n):
   print('%s is runing' %os.getpid())
   time.sleep(random.randint(1,3))
   return n**2

if __name__ == '__main__':

   executor=ThreadPoolExecutor(max_workers=3)

   # for i in range(11):
   #     future=executor.submit(task,i)

   executor.map(task,range(1,12)) #map取代了for+submit

 

4.回调函数

可以为进程池或线程池内的每个进程或线程绑定一个函数,该函数在进程或线程的任务执行完毕后自动触发,并接收任务的返回值当作参数,该函数称为回调函数

from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor
from multiprocessing import Pool
import requests
import json
import os

def get_page(url):
   print('<进程%s> get %s' %(os.getpid(),url))
   respone=requests.get(url)
   if respone.status_code == 200:
       return {'url':url,'text':respone.text}

def parse_page(res):
   res=res.result()
   print('<进程%s> parse %s' %(os.getpid(),res['url']))
   parse_res='url:<%s> size:[%s]\n' %(res['url'],len(res['text']))
   with open('db.txt','a') as f:
       f.write(parse_res)


if __name__ == '__main__':
   urls=[
       'https://www.baidu.com',
       'https://www.python.org',
       'https://www.openstack.org',
       'https://help.github.com/',
       'http://www.sina.com.cn/'
  ]

   p=ProcessPoolExecutor(3)
   for url in urls:
       p.submit(get_page,url).add_done_callback(parse_page) #parse_page拿到的是一个future对象obj,需要用obj.result()拿到结果

 

实际上,concurrent所提供的线程池是较为粗糙的,最好的方式还是自己建造线程池。之后将讨论

 

——本文内容参考学习路飞学城python大纲以及http://www.cnblogs.com/linhaifeng/

posted @ 2018-07-29 18:37  TAKAFTER  阅读(123)  评论(0)    收藏  举报