2019.9.19学习内容及小结

时间:2019-09-20
本文章向大家介绍2019.9.19学习内容及小结,主要包括2019.9.19学习内容及小结使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

小结
上节课回顾

'''
线程锁(Lock):本质就是互斥锁(*****)
    使用目的:保证数据安全
死锁问题:线程1抢到了锁A没释放
          线程2抢到了锁B没释放
          线程1往下执行需要锁B
          线程2往下执行需要锁A
        解决方案:递归锁(RLock)
        特点:只有同一个线程下可以多次acquire,acquire了几次就要release几次

信号量:
       实现:定制了锁的个数,也就意味着最多有几个线程可以抢到锁头,这几个线程就可以实现并发

GIL(****)
什么是GIL:
Cpython中有GIL锁(全局解释器锁),GIL锁本质就是一把互斥锁,
  GIL锁导致了 python 的同一个进程同一时刻下只有一个线程在执行代码
为什么要有?
因为Cpython自带的垃圾回收机制不是线程安全的,所以要有GIL锁

多进程vs多线程(*****)
计算密集型任务===》推荐使用多进程解决===》要利用多核优势,并行的去计算
io密集型任务===》推荐使用多线程解决===》大部分时间都在io,并且开启一个线程比开启一个进程速度要快的多
大部分的需求都是io密集型,因为大部分的软件都是基于网络的(存在网络io)

cpu主要负责计算


########### 今日内容 ###########
线程queue 线程定时器 (*)
线程queue三种用法
1.先进先出
2.先进后出
3.优先级    优先级判断  通常用数字表示  数字小的先出

多线程并发socket服务端(*****)
  具体看代码,要看明白

进程池,线程池(****)
  必须把进程池、线程池的概念搞懂
  能看懂代码

协程:



'''

线程queue

###### 先进先出 ######
# import queue
# q = queue.Queue() #拿到线程queue 对象
# q.put('123') # 往queue里放数据
# q.put('456')
# q.put('789')
#
# print(q.get()) #123  #取出 queue里面的数据(这是先进先出)
# print(q.get()) #456
# print(q.get()) #789
# q.task_done()
# q.task_done()
# q.task_done()
# q.join()
'''
task_done() 和 join()
每次从queue中get一个数据之后,当处理好相关问题,最后调用该方法,
以提示q.join()是否停止阻塞,让线程向前执行或者退出;
q.join(),阻塞,直到queue中的数据均被删除或者处理。为队列中的每一项都调用一次
如:上述例子,如果put了三次(相当于放了三个值),而只get了两次(取
出了两个值),然后用了两次task_done(),那么最后的join()会阻塞,
知道把队列里的数据全部处理完后才停止阻塞
'''

######## 堆栈 先进后出 #########
# import queue
# q = queue.LifoQueue() #堆栈   先进后出
# q.put('123')
# q.put('456')
# q.put('789')
#
# print(q.get())#789
# print(q.get())#456
# print(q.get())#123


######## 优先级 ########
import queue
q = queue.PriorityQueue() #可以根据优先级取数据
# 通常这个元组的第一个值是int类型 数小的先出
q.put((50, '123'))
q.put((60, '456'))
q.put((10, '789'))

print(q.get()) #(10, '789')
print(q.get()) #(50, '123')
print(q.get()) #(60, '456')

线程定时器

from threading import Timer
import time

def task():
    print('线程执行了')
    time.sleep(2)
    print('线程结束了')

t = Timer(5, task) #定时5s后开启线程,不会阻塞下面的代码
t.start()
print('123123') #正常执行,123123

进程池和线程池

'''
进程池线程池:
    池的功能限制进程数或线程数。
    什么时候需要限制?
    当并发的任务数量远远大于计算机所能承受的范围,
    即无法一次性开启过多的任务数量,就应该考虑去限制我进程数或者线程数,
    从而保证服务器不蹦
'''
#1.导入进程池或线程池模块
from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor
from threading import currentThread
from multiprocessing import current_process
import time
# 2.构造一个工作
def task(i):
    #print(f'{currentThread().name} 在执行 {i}')
    print(f'{current_process().name} 在执行 {i}')
    time.sleep(1)
    return i**2

if __name__ == '__main__':
    #pool = ThreadPoolExecutor(4) # 池子里只有4个线程
    #3.定制池中进程或线程 数量
    pool = ProcessPoolExecutor(4) #池子里只有4个进程
    fu_list = []
    # 首先考虑开启任务数量(开启任务较多考虑使用进程池线程池)
    for i in range(20):
        #pool.submit(task, i) #提交池,把要处理的函数名及其它参数放进去
        #4. 提交池,传相应参数
        future = pool.submit(task, i) #task任务要做20次,4个进程负责做这个事
        #print(future.result()) #如果没有结果一直等待拿到结果,这样导致了所有任务都在串行
        fu_list.append(future)
    #5.关闭池
    pool.shutdown() #关闭了池入口,会等待所有的任务执行完,结束阻塞
    for fu in fu_list:
        #6.打印结果
        print(fu.result())

进程池和线程池02

'''
进程池线程池:
        池的功能限制进程数或线程数
        什么时候限制?
        当并发的任务数量远远大于计算机所能承受的范围,即无法一次性开启过多的任务数量
        这时就应该考虑去限制开启的进程数或线程数,从而保证服务器不崩

理解为提交任务的两种方式
同步:提交了一个任务,必须等任务执行完了(拿到返回值),才能执行下一行代码

异步:提交了一个任务,,不要等执行完了,可以直接执行下一行代码

'''

from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor
from threading import currentThread
from multiprocessing import current_process
import time

def task1(i):
    #print(f'{currentThread().name} 在执行 {i}')
    print(f'{current_process().name} 在执行 {i}')
    time.sleep(1)
    return i**2

def parse(future):
    # 处理拿到的结果
    print(future.result())

if __name__ == '__main__':
    #pool = ThreadPoolExecutor(4)
    pool = ProcessPoolExecutor(4)
    for i in range(20):
        future = pool.submit(task1, i)
        future.add_done_callback(parse)
        # 为当前任务绑定一个函数,在当前任务执行结束的时候会触发这个函数
        #会把future对象作为参数传函数
        #这个称之为回调函数,处理完了回来就调用这个函数

协程

'''
python的 线程用的是操作系统 原生的线程

协程:单线程下实现并发
     并发:切换+保存状态
     多线程:操作系统帮你实现的(同一进程同一时间只能运行一个线程),如果遇到io会被切换,执行时间过长也会切换,实现一个雨露均沾的效果

     什么样的协程是有意义的?
        遇到io切换的时候才有意义
        具体:
        协程概念本质是程序员抽象出来的,操作系统根本不知道协程的存在,
        也就是说来了一个线程我自己遇到io,我自己线程内部直接切到自己
        的别的任务上,操作系统根本发现不了,也就是实现了单线程下效率最高

     优点:
        自己控制切换要比操作系统切换快的多
     缺点:
        对比多线程
        自己要检测所有的io,但凡有一个阻塞整体都要跟着阻塞

        对比多进程
        无法利用多核优势
为什么要有协程(遇到io切换 )?
     自己控制切换要比操作系统切换快的多,降低了单个线程的io时间

'''

# import time
# def eat():
#     print('eat 1')
#     # 疯狂的计算没有io
#     time.sleep(2)
#
# def play():
#     print('play 1')
#     # 疯狂的计算没有io
#     time.sleep(3)

# play()
# eat() #5s

# import time
# def func():
#     while True:
#         1000000+1
#         yield
# def func2():
#     g = func()
#     for i in range(100000000):
#         a=i + 1
#         next(g)
#         print(a)
# start = time.time()
# func2()
# end = time.time()
# print(end - start) #24.284388780593872

import time
def func():
    for i in range(100000000):
        i + 1
def func2():
    for i in range(100000000):
        i + 1
start= time.time()
func()
func2()
end= time.time()
print(end - start) #16.235928535461426

'''对比:通过yeild切换运行的时间反而比串行 更消耗时间,这样实现的协程是没有意义的'''

协程02

from gevent import monkey
monkey.patch_all() #打了一个补丁,可以实现捕获非gevent的io
import gevent
import time

def eat():
    print('eat 1')
    time.sleep(2)
    print('eat 2')
def play():
    print('play 1')
    time.sleep(3)
    print('play 2')

start = time.time()
g1 = gevent.spawn(eat)
g2 = gevent.spawn(play)
g1.join()
g2.join()
end = time.time()
print(end - start) #3.00417160987854

### 利用gvent模块在捕获非gvent的io,实现单线程并发,提高效率

原文地址:https://www.cnblogs.com/chmily/p/11557159.html