python学习---进程和线程(2)线程、协程

多线程

多线程类似于同时执行多个不同程序

  • 每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
  • 每个线程都有他自己的一组CPU寄存器,称为线程的上下文,该上下文反映了线程上次运行该线程的CPU寄存器的状态。
  • 指令指针和堆栈指针寄存器是线程上下文中两个最重要的寄存器,线程总是在进程得到上下文中运行的,这些地址都用于标志拥有线程的进程地址空间中的内存。
  • 线程可以被抢占(中断)。
  • 在其他线程正在运行时,线程可以暂时搁置(也称为睡眠) 这就是线程的退让。

优点

  • 使用线程可以把占据长时间的程序中的任务放到后台去处理。
  • 用户界面可以更加吸引人,比如用户点击了一个按钮去触发某些事件的处理,可以弹出一个进度条来显示处理的进度。
  • 程序的运行速度可能加快。
  • 在一些等待的任务实现上如用户输入、文件读写和网络收发数据等,线程就比较有用了。在这种情况下我们可以释放一些珍贵的资源如内存占用等等。

分类

  • 内核线程:由操作系统内核创建和撤销。
  • 用户线程:不需要内核支持而在用户程序中实现的线程。

步骤

  • 新建
  • 就绪
  • 运行
  • 阻塞
  • 结束
import threading
from time import sleep


def download(n):
    img = ['a.jpg','b.jpg','c.jpg']
    for i in img:
        print('downloading:',i)
        sleep(n)#让出cpu使用权
        print('donei',i)
def listenmusic():
    musics=['11','22','33','44']
    for m in musics:
        print('listening:',m)
        sleep(0.5)
        print('donem',m)
if __name__ == '__main__':

    t1 = threading.Thread(target=download,name='aa',args=(1,))
    t1.start()

    t2 = threading.Thread(target=listenmusic,name='aa')
    t2.start()
"""
downloading: a.jpg
listening: 11
donem 11
listening: 22
donei a.jpg
downloading: b.jpg
donem 22
listening: 33
donem 33
listening: 44
donei b.jpg
downloading: c.jpg
donem 44
donei c.jpg

Process finished with exit code 0

"""

共享数据

线程可以共享全局变量,当共享数据时要考虑数据的安全问题。

共享数据:

  • 如果多个线程共同对某个数据修改,则可能出现不可预料的结果,为了保证数据的正确性,需要对多个进程进行同步

同步:
一个一个完成,一个做完另一个才能进来。效率会降低

多线程的优势在于可以同时运行多个任务(python的多线程是伪多线程

import threading

money =1000
def run1():
    global money
    for i in range(100):
        money-=1

if __name__ == '__main__':

    t1 = threading.Thread(target=run1,name='aa')
    t2 = threading.Thread(target=run1,name='bb')

    t1.start()
    t2.start()
    t1.join()
    t2.join()
    print(money)
 #800

当线程需要共享数据时,可能存在数据不同步的问题。为了避免这种情况,引入锁的概念。
python多线程存在全局解释器锁GIL,为保证数据的安全,线程默认加锁。一个线程执行完后才允许另一个线程执行,加锁后线程同步,速度慢但数据安全。
当数据运算足够大时,会默认把锁释放,此时数据不再安全。

import threading

money =0
def run1():
    global money
    for i in range(10000000):
        money+=1
    print('1:',money)
def run2():
    global money
    for i in range(10000000):
        money+=1
    print('2:',money)

if __name__ == '__main__':

    t1 = threading.Thread(target=run1,name='aa')
    t2 = threading.Thread(target=run2,name='bb')

    t1.start()
    t2.start()
    t1.join()
    t2.join()
    print(money)
   """
1: 11693709
2: 12647085
12647085
   """

当计算密集计算量大时使用进程。
当有耗时操作时使用线程。如爬虫,下载,I/O操作等

线程同步

使用Thread对象的Lock和Rlock可以实现简单的线程同步

  • lock.acquire()阻塞
  • lock.release()释放

由于python默认上锁,所以在数据量运算小的情况下写不写lock都是带锁的

import threading
import time

lock = threading.Lock()
l1 = [0]*10
def task1():
    #获取线程锁,如果以及上锁,则阻塞等待锁释放
    lock.acquire()#阻塞
    for i in range(len(l1)):
        l1[i]=1
        time.sleep(0.5)
    lock.release()
def task2():
    lock.acquire()#阻塞
    for i in range(len(l1)):
        print('-------->',l1[i])
        time.sleep(0.5)
    lock.release()

if __name__ == '__main__':
    t1 = threading.Thread(target=task1)
    t2 = threading.Thread(target=task2)


    t2.start()
    t1.start()

    t2.join()
    t1.join()

    print(l1)
"""
--------> 0
--------> 0
--------> 0
--------> 0
--------> 0
--------> 0
--------> 0
--------> 0
--------> 0
--------> 0
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
"""

死锁

如果两个线程分别占用一部分资源并同时等待对方的资源,就会造成死锁

避免死锁方法
  1. 重构代码
  2. lock.acquire(timeout=)超时就会释放
  3. 砸掉电脑
import threading
from threading import Thread,Lock
import time
lock1 = threading.Lock()
lock2 = threading.Lock()

class MyThread1(Thread):
    def run(self) :
        if lock1.acquire():
            print(self.name+'get 1')
            time.sleep(0.1)
            if lock2.acquire(timeout=2):
                print(self.name+'get AB')
                lock2.release()
            lock1.release()
class MyThread2(Thread):
    def run(self) :
        if lock2.acquire():
            print(self.name+'get 2')
            time.sleep(0.1)
            if lock1.acquire():
                print(self.name+'get 1 2 ')
                lock1.release()
            lock2.release()
if __name__ == '__main__':

    t1 = MyThread1()
    t2 = MyThread2()
    t1.start()
    t2.start()
#线程1:拿1,期待2,如果2等待超时,释放1
#线程2:拿2,期待1
"""
Thread-1get 1
Thread-2get 2
Thread-2get 1 2 
"""

两个线程之间的通信:生产者和消费者模式

协程

微线程

适用于耗时操作:网络请求,网络下载(爬虫),IO操作
高效利用CPU

通过生成器完成

def task1():
    for i in range(3):
        print('A'+str(i))
        yield
        time.sleep(1)
def task2():
    for i in range(3):
        print('B'+str(i))
        yield
        time.sleep(2)
if __name__ == '__main__':
    g1 = task1()
    g2 = task2()
    while True:
        try:
            next(g1)
            next(g2)
        except:
            break
"""
A0
B0
A1
B1
A2
B2
"""

使用greenlet

不够智能,需要手动切换

  • g1 = greenlet(a)
  • switch()进行切换
from  greenlet import greenlet
def a():
    for i in range(5):
        print('A' + str(i))
        g2.switch()
        time.sleep(0.5)
def b():
    for i in range(5):
        print('B' + str(i))
        g3.switch()
        time.sleep(0.5)
def c():
    for i in range(5):
        print('C' + str(i))
        g1.switch()
        time.sleep(0.5)
if __name__ == '__main__':
    g1 = greenlet(a)
    g2 = greenlet(b)
    g3 = greenlet(c)
    g1.switch()

使用gevent

底层还是greenlet,可以自动切换
g1 = gevent.spawn(a)

猴子补丁 monkey patch

检测到耗时操作,自动切换

  • from gevent import monkey
  • monkey.patch_all()
import time
import gevent
from gevent import monkey
monkey.patch_all()
def a():
    for i in range(5):
        print('A' + str(i))

        time.sleep(0.5)
def b():
    for i in range(5):
        print('B' + str(i))

        time.sleep(0.5)
def c():
    for i in range(5):
        print('C' + str(i))

        time.sleep(0.5)
if __name__ == '__main__':
    g1 = gevent.spawn(a)
    g2 = gevent.spawn(b)
    g3 = gevent.spawn(c)
    g1.join()
    g2.join()
    g3.join()

案例

import urllib.request
import gevent
from gevent import monkey
monkey.patch_all()
def download(url):
    response = urllib.request.urlopen(url)#耗时操作
    content = response.read()
    print('下载了{}的数据,长度:{}'.format(url,len(content)))
if __name__ == '__main__':
    urls = ['https://www.qq.com','https://www.baidu.com','https://www.weibo.com']
    g1 = gevent.spawn(download,urls[0])
    g2 = gevent.spawn(download, urls[1])
    g3 = gevent.spawn(download, urls[2])
    g1.join()
    g2.join()
    g3.join()
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值