并发编程之线程2

 1、GIL锁:
1、什么是GIL
全局解释器锁,本质就是一把互斥锁,是加到解释器身上的,每一个python进程内都有这么一把锁
2、有了GIL会对单进程下的多个线程造成什么样的影响
多线程要想执行,首先需要争抢GIL,对所有待执行的线程来说,GIL就相当于执行权限,
同一时刻只有一个线程争抢成功,即单进程下的多个线程同一时刻只有一个在运行
意味着单进程下的多线程没有并行的效果,但是有并发的效果

ps:分散于不同进程内的线程不会去争抢同一把GIL,只有同一个进程的多个线程才争抢同一把GIL

3、为什么要有GIL
Cpython解释器的内存管理机制不是线程安全的,所以需要有一把控制同一时间解释器的使用权

4、GIL与自定义互斥锁的异同,多个线程争抢GIL与自定义互斥锁的过程分析
相同:
都是互斥锁
不同点:
GIL是加到解释器上的,作用于全局
自定义互斥锁作用于局部

单进程内的所有线程都会去抢GIL
单进程内的只有一部分线程会去抢自定义的互斥锁


5、什么时候用python的多线程,什么时候用多进程,为什么?
单进程下的多个线程是无法并行,无法并行意味着不能利用多核优势

cpu干的计算的活,多个cpu意味提升了计算性能,
cpu是无法做IO操作,多个cpu在IO操作面前毫无用处

当我们的程序是IO密集型的情况下,多核对性能的提升微不足道,此时可以使用python的多线程
当我们的程序是计算密集型的情况下,一定要用上多核优势,此时可以使用python的多进程



2、进程池vs线程池
池:装固定数目的东西,东西指的是进程或者线程
         池子内什么时候装进程:并发的任务属于计算密集型
池子内什么时候装线程:并发的任务属于IO密集型

为什么要用?
让机器在自己可承受的范围内去保证一个高效的工作

from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor
import time,os,random

def task(x):
print('%s 接客' %x)
time.sleep(random.randint(1,3))
return x**2

if __name__ == '__main__':
# 异步调用
p=ThreadPoolExecutor(4) # 默认开启的线程数是cpu的核数*5

# alex,武佩奇,杨里,吴晨芋,张三

obj_l=[]
for i in range(10):
obj=p.submit(task,i)
obj_l.append(obj)

# p.close()
# p.join()
p.shutdown(wait=True)
# shutdown指的是不能再往进程池内提交任务,wait=True等待进程池或线程池内所有的任务都运行完毕


print(obj_l[3].result())
print('主')
 

 


  3、阻塞vs非阻塞 同步vs异步
阻塞、就绪、运行指的是进程的三种执行状态
阻塞=》阻塞:遇到io操作
非阻塞=》就绪、运行

总结:阻塞与非阻塞指的是程序的执行状态


同步与异步:指的是提交任务的两种方式
同步:提交完任务后就在原地等待,直到任务运行完毕,拿到返回值,才继续执行下一行代码
异步:提交完任务后不在原地等待,直接执行下一行代码,结果?
1、阻塞与非阻塞指的是程序的两种运行状态
阻塞:遇到IO就发生阻塞,程序一旦遇到阻塞操作就会停在原地,并且立刻释放CPU资源
非阻塞(就绪态或运行态):没有遇到IO操作,或者通过某种手段让程序即便是遇到IO操作也不会停在原地,执行其他操作,力求尽可能多的占有CPU


2、同步与异步指的是提交任务的两种方式:
同步调用:提交完任务后,就在原地等待,直到任务运行完毕后,拿到任务的返回值,才继续执行下一行代码
异步调用:提交完任务后,不在原地等待,直接执行下一行代码,结果?

 

4、异步+回调机制
from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor
from threading import current_thread
import requests
import os
import time
import random

def get(url):
    print('%s GET %s' %(current_thread().name,url))
    response=requests.get(url)
    time.sleep(random.randint(1,3))

    if response.status_code == 200:
        # 干解析的活
        return response.text     #等到下载功能完成,返回结果

def pasrse(obj):
    res=obj.result()
    print('%s 解析结果为:%s' %(current_thread().name,len(res)))

if __name__ == '__main__':
    urls=[
        'https://www.baidu.com',
        'https://www.baidu.com',
        'https://www.baidu.com',
        'https://www.baidu.com',
        'https://www.baidu.com',
        'https://www.baidu.com',
        'https://www.baidu.com',
        'https://www.baidu.com',
        'https://www.python.org',
    ]

    pool=ThreadPoolExecutor(4)     #指定同一时刻只能有4个线程在工作
    for url in urls:
        obj=pool.submit(get,url)
        obj.add_done_callback(pasrse)     #把下载结果给到解析功能解析,实现下载与解析的并发

   
    print('主线程',current_thread().name)   #解析功能其实是主线程做完的,因为此时只有主线程空闲

 

线程的其他属性方法(了解):queue,Event
import queue

# q=queue.Queue(3) #队列:先进先出
# q.put(1)
# q.put(2)
# q.put(3)
# # q.put(4)
#
# print(q.get())
# print(q.get())
# print(q.get())


# q=queue.LifoQueue(3) #堆栈:后进先出
#
# q.put('a')
# q.put('b')
# q.put('c')
#
# print(q.get())
# print(q.get())
# print(q.get())


q=queue.PriorityQueue(3) #优先级队列:可以以小元组的形式往队列里存值,第一个元素代表优先级,数字越小优先级越高
q.put((10,'user1'))
q.put((-3,'user2'))
q.put((-2,'user3'))


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

 

Event:
线程的一个关键特性是每个线程都是独立运行且状态不可预测。如果程序中的其 他线程需要通过判断某个线程的状态来确定自己下一步的操作,这时线程同步问题就会变得非常棘手。为了解决这些问题,我们需要使用threading库中的Event对象。 对象包含一个可由线程设置的信号标志,它允许线程等待某些事件的发生。在 初始情况下,Event对象中的信号标志被设置为假。如果有线程等待一个Event对象, 而这个Event对象的标志为假,那么这个线程将会被一直阻塞直至该标志为真。一个线程如果将一个Event对象的信号标志设置为真,它将唤醒所有等待这个Event对象的线程。如果一个线程等待一个已经被设置为真的Event对象,那么它将忽略这个事件, 继续执行
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()
event.isSet():返回event的状态值;

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

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

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

 




posted @ 2018-04-28 15:55  鲁之敬  阅读(74)  评论(0编辑  收藏  举报