107、进程与线程
进程、线程、与协成
1.1 进程介绍
程序并不能单独运行,只有将程序装载到内存中,系统为它分配资源才能运行,而这种执行的程序就称之为进程。
程序和进程的区别就在于:程序是指令的集合,它是进程运行的静态描述文本;进程是程序的一次执行活动,属于动态概念。
在多道编程中,我们允许多个程序同时加载到内存中,在操作系统的调度下,可以实现并发地执行。这是这样的设计,大大提高了CPU的利用率。进程的出现让每个用户感觉到自己独享CPU,因此,进程就是为了在CPU上实现多道编程而提出的。
进程的缺点:
- 
进程只能在一个时间干一件事,如果想同时干两件事或多件事,进程就无能为力了。 
- 
进程在执行的过程中如果阻塞,例如等待输入,整个进程就会挂起,即使进程中有些工作不依赖于输入的数据,也将无法执行。 
1.2 线程介绍
线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务
1.3 线程和进程的区别
- 进程的内存独立,线程共享同一进程的内存
- 进程是资源的集合,线程是执行单位
- 进程之间不能之间互相访问,线程可以相互通信
- 创建新进程非常消耗资源,线程非常轻量,只保存线程需要运行时的必要数据,如上下文
- 同一进程里的线程可以相互控制,父线程可以控制子线程
- io 密集型应用使用多线程,
- 计算密集型应用使用多进程
1.4 GIL 锁
python 中某一进程中的多个线程只能被一个 CPU 调用,因为在线程中存在 GIL(全局解释器锁)。而C语言一个进程的多线程可以分别被不同的 CPU 调用
2、threading模块
2.1 线程的继承
import threading
import time
class MyThread(threading.Thread):
    def __init__(self,num):
        threading.Thread.__init__(self)
        self.num = num
    def run(self):#定义每个线程要运行的函数
        print("running on number:%s" %self.num)
        time.sleep(3)
if __name__ == '__main__':
    t1 = MyThread(1)
    t2 = MyThread(2)
    t1.start()
    t2.start()
2.2 创建多线程与join
import threading,time
def hellow(msg):
    print('this is hellow %s' %msg)
    time.sleep(1)
if __name__ == '__main__':
    thread_list = []
    for i in range(10):
        t1 = threading.Thread(target=hellow,args=(i,))#生成一个线程
        t1.start()
        thread_list.append(t1)
        print(t1.getName())#获取线程名
    for r in thread_list:
        r.join()#等待该线程执行完毕再往下执行,join(2)就是等待的最长时间
    print('-------主线程-------')
2.3 守护线程Daemon
import threading,time
'''守护线程会在主线程执行完毕时立刻断开'''
def sayhi(msg):
    time.sleep(0.001)
    print('this is msg',msg)
if __name__ == '__main__':
    for i in range(10):
        t1 = threading.Thread(target=sayhi,args=(i,))
        t1.setDaemon(True)#将 t1 设为主线程的守护线程
        t1.start()
    print('------主线程------')
每次输出的结果会有不同,有的线程没执行完就跟着主线程down了
2.4 小结
- start 线程准备就绪,等待CPU调度
- setName 为线程设置名称
- getName 获取线程名称
- setDaemon 设置为后台线程或前台线程(默认),如果是后台线程,主线程执行过程中,后台线程也在进行,主线程执行完毕后,后台线程不论成功与否,均停止;如果是前台线程,主线程执行过程中,前台线程也在进行,主线程执行完毕后,等待前台线程也执行完成后,程序停止
- join 逐个执行每个线程,执行完毕后继续往下执行,该方法使得多线程变得无意义
- run 线程被cpu调度后自动执行线程对象的run方法
2.5 线程锁
单线程锁 Lock、RLock
import threading,time
num = 10
#lock = threading.Lock()#创建线程锁,一次只能锁一个,只能一次申请锁
lock = threading.RLock()#创建递归锁,一次只能锁一个,多次次申请锁
#lock = threading.BoundedSemaphore(3)#创建信号量锁,一次锁定3人
def task(arg):
    lock.acquire()#申请使用锁
    lock.acquire()#RLock多次申请使用锁,递归锁
    time.sleep(1)
    global num
    num -= 1
    print(num)
    lock.release()#释放锁
    lock.release()#RLock多次释放锁
for i in range(10):
    t = threading.Thread(target=task,args=[i,])
    t.start()
print('-----主线程-----',num)
多线程 Semaphore
lock = threading.BoundedSemaphore(3)#三线程锁
def task(arg):
    # 申请使用锁,其他人等
    lock.acquire()
    time.sleep(1)
    global v
    v -= 1
    print(v)
    # 释放
    lock.release()
    
for i in range(10):
    t = threading.Thread(target=task,args=(i,))
    t.start()
事件 event
python线程的事件用于主线程控制其他线程的执行,事件主要提供了三个方法 set、wait、clear。
事件处理的机制:全局定义了一个“Flag”,如果“Flag”值为 False,那么当程序执行 event.wait 方法时就会阻塞,如果“Flag”值为True,那么event.wait 方法时便不再阻塞。
- clear:将“Flag”设置为False
- set:将“Flag”设置为True
lock = threading.Event()
def task(arg):
    time.sleep(1)
    # 锁住所有的线程
    lock.wait()
    # 申请使用锁,其他人等
    print(arg)
for i in range(10):
    t = threading.Thread(target=task,args=(i,))
    t.start()
while True:
    value = input('>>>>')
    if value == '1':
        lock.set()
        lock.clear()
条件 Condition
使得线程等待,只有满足某条件时,才释放n个线程
lock = threading.Condition()
def task(arg):
    time.sleep(1)
    # 锁住所有的线程
    lock.acquire()
    lock.wait()
    # 申请使用锁,其他人等
    print('线程',arg)
    lock.release()
    
for i in range(10):
    t = threading.Thread(target=task,args=(i,))
    t.start()
while True:
    value = input('>>>>')
    lock.acquire()
    lock.notify(int(value))
    lock.release()
定时器 Timer
定时器,指定n秒后执行某操作
from threading import Timer
def hello():
    print("hello, world")
t = Timer(1, hello)
t.start()  # after 1 seconds, "hello, world" will be printed
2.5 线程池
from concurrent.futures import ThreadPoolExecutor
import time,requests
pool = ThreadPoolExecutor(2)#定义线程池每次最多运行两个线程
url_list = [
    'http://www.baidu.com',
    'http://www.oldboyedu.com',
    'http://www.qq.com',
 ]
def download(url):
    response = requests.get(url)
    #print(response)
    return response
def option(future):
    download_response = future.result()
    print('option',download_response.url,download_response.status_code,download_response.text)
for url in url_list:
    print('开始请求',url)
    future = pool.submit(download,url)#去线程池获取连接
    future.add_done_callback(option)#调用option函数,并将download的结果传参
3、multiprocessing 模块
进程各自持有一份数据,默认无法共享数据
from multiprocessing import Process
import time
def option(num,li):
    li.append = num
    print(li)
if __name__ == '__main__':
    v = []
    # for i in range(5):
    #     p = threading.Thread(target=option,args=(i,v,))
    #     p.start()
    for i in range(5):
        p1 = Process(target=option,args=(i,v,))
        p1.start()
3.1 进程间数据共享
方法一:Array
from multiprocessing import Process,Array,Manager
import threading
def option(num,li):
    li[num] = 1
    print(list(li))
if __name__ == '__main__':
    v = Array('i',5)#('数据类型',长度)
    for i in range(5):
        p = Process(target=option,args=(i,v,))
        p.start()
方法二:manage.dict()共享数据
from multiprocessing import Process,Array,Manager
import threading
def option(num,li):
    li.append(num)
    print('打印信息',li)
if __name__ == '__main__':
    v = Manager().list()
    for i in range(10):
        p = Process(target=option,args=(i,v,))
        p.start()
        p.join()
3.2 进程池
进程锁与线程锁一样
进程池内部维护一个进程序列,当使用时,则去进程池中获取一个进程,如果进程池序列中没有可供使用的进进程,那么程序就会等待,直到进程池中有可用进程为止。
from concurrent.futures import ProcessPoolExecutor
from multiprocessing import Process,Manager
def call(arg):
    data = arg.result()
    print(data)
def option(arg):
    print('打印信息',arg)
    return arg + 100
if __name__ == '__main__':
    pool = ProcessPoolExecutor(5)
    for i in range(10):
        obj = pool.submit(option,i)
        obj.add_done_callback(call)
4、协成
协程存在的意义:对于多线程应用,CPU通过切片的方式来切换线程间的执行,线程切换时需要耗时(保存状态,下次继续)。协程,则只使用一个线程,在一个线程中规定某个代码块执行顺序。
协程的适用场景:当程序中存在大量不需要CPU的操作时(IO),适用于协程;
from greenlet import greenlet
def test1():
    print(12)
    gr2.switch()
    print(34)
    gr2.switch()
def test2():
    print(56)
    gr1.switch()
    print(78)
gr1 = greenlet(test1)
gr2 = greenlet(test2)
gr1.switch()
根据协程二次开发:协程+IO
from gevent import monkey; monkey.patch_all()
import gevent
import requests
def f(url):
    response = requests.get(url)
    print(response.url,response.status_code)
gevent.joinall([
        gevent.spawn(f, 'http://www.oldboyedu.com/'),
        gevent.spawn(f, 'http://www.baidu.com/'),
        gevent.spawn(f, 'http://github.com/'),
])
 
                    
                     
                    
                 
                    
                 
 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号