多线程、协程、多进程,小白的看法

74 篇文章 2 订阅

多线程 vs 多进程

  • 每个cpu在同一时间只能执行一个线程,在单核cpu下的多线程只是并发并不是并行,存在时间间隔而不是在同一时间内进行
  • 由于python里面全局解释器gil的存在,在一个python的进程中,gil只有一个,拿不到gil的线程,没有办法进入cpu执行
  • 程序:一堆代码以文本形式存入文档
  • 进程: 程序运行状态 - 包含地址空间,内存,栈等
  • 每个进程又由自己的独立环境,多进程共享数据是一个问题
  • 线程 - 一个进程的独立运行片段,一个进程可以由多个线程构成
  • 一个进程的多个线程共享数据和上下文运行环境
  • 共享的数据可能会由互斥的问题

全局解释器锁(GIL)

  • python是伪装多线程,并不是真的多线程 - 提高同一时间资源利用率 - 其实电脑cpu据大部分都在空余,多进程就能完美利用cpu
  • python包
    - _threading:包 - 如果多线程用这个包的话,不会等待其他多线程的,只会传达指令,如果想要程序完美结束,可以选择让主线程睡眠
    - _threading.start_new_thread(函数名,参数tuple类型)
import time
import _thread
def loop1():
    print('start loop1',time.ctime())
    time.sleep(4)
    print('end loop1',time.ctime())
def loop2():
    print('start loop2',time.ctime())
    time.sleep(2)
    print('end loop2',time.ctime())
def main():
    print(time.ctime())
    _thread.start_new_thread(loop1, ())
    _thread.start_new_thread(loop2, ())
    print(time.ctime())
if __name__ == '__main__':
    main()
    while True:
        time.sleep(1)
  • threading:包
  • 直接利用threading.Thread生成实例化
  • t = threading.Thread(target ,args)
    t.start:启动多线程
    t.join:等待多线程执行完成
  • 执行顺序不定
import time
import threading


def loop1():
    print('start loop1',time.ctime())
    time.sleep(4)
    print('end loop1',time.ctime())


def loop2():
    print('start loop2',time.ctime())
    time.sleep(2)
    print('end loop2',time.ctime())


def main():
    print(time.ctime())
    t1 = threading.Thread(target=loop1,args=())
    t1.start()
    t2 = threading.Thread(target=loop2,args=())
    t2.start()
    print(time.ctime())


if __name__ == '__main__':
    main()
  • 守护线程 - 如果在程序中将子线程设计成守护线程,守护线程会在主线程结束的时候自动退出 - 守护线程中不重要并且不允许脱离主线程 - 很鸡肋,一般不推荐使用
  • 用t.setdemon(true)就行
  • 线程常用属性
  • 子类:继承到threading.Threading
创建自己的多线程类
import threading 
from time import sleep, ctime 
 
class MyThread(threading.Thread):

    def __init__(self,func,args,name=''):
        threading.Thread.__init__(self)
        self.name=name
        self.func=func
        self.args=args
    
    def run(self):
        apply(self.func,self.args)


def super_play(file,time):
    for i in range(2):
        print 'Start playing: %s! %s' %(file,ctime())
        sleep(time)


list = {'爱情买卖.mp3':3,'阿凡达.mp4':5}

#创建线程
threads = []
files = range(len(list))

for k,v in list.items():
    t = MyThread(super_play,(k,v),super_play.__name__)
    threads.append(t)        

if __name__ == '__main__': 
    #启动线程
    for i in files:
        threads[i].start() 
  for i in files:
      threads[i].join()

    #主线程
    print 'end:%s' %ctime()
  • 每一个进程里面都由一个解释器,因此,推荐多进程而不是多线程,通过并行提高效率
    共享变量
  • 多线程访问同一个变量,产生共享变量
 import threading

sum = 0
loopSum = 1000000

def myAdd():
    global  sum, loopSum
    for i in range(1, loopSum):
        sum += 1

def myMinu():
    global  sum, loopSum
    for i in range(1, loopSum):
        sum -= 1

if __name__ == '__main__':
    print("Starting ....{0}".format(sum))

    # 开始多线程的实例,看执行结果是否一样
    t1 = threading.Thread(target=myAdd, args=())
    t2 = threading.Thread(target=myMinu, args=())

    t1.start()
    t2.start()

    t1.join()
    t2.join()

    print("Done .... {0}".format(sum))
不是原址操作,会共享变量
  • 解决方法:锁
    是一个标准,表示一个线程正在占用资源
  • 锁的使用
    由于线程之间随机调度:某线程可能在执行n条后,CPU接着执行其他线程。为了多个线程同时操作一个内存中的资源时不产生混乱,我们使用锁。Lock(指令锁)是可用的最低级的同步指令。Lock处于锁定状态时,不被特定的线程拥有。Lock包含两种状态——锁定和非锁定,以及两个基本的方法。可以认为Lock有一个锁定池,当线程请求锁定时,将线程至于池中,直到获得锁定后出池。池中的线程处于状态图中的同步阻塞状态。RLock(可重入锁)是一个可以被同一个线程请求多次的同步指令。RLock使用了“拥有的线程”和“递归等级”的概念,处于锁定状态时,RLock被某个线程拥有。拥有RLock的线程可以再次调用acquire(),释放锁时需要调用release()相同次数。可以认为RLock包含一个锁定池和一个初始值为0的计数器,每次成功调用 acquire()/release(),计数器将+1/-1,为0时锁处于未锁定状态。
import threading
import time

gl_num = 0

def show(arg):
    global gl_num
    time.sleep(1)
    gl_num +=1
    print gl_num

for i in range(10):
    t = threading.Thread(target=show, args=(i,))
    t.start()

print 'main thread stop'


main thread stop
12

 3
4
568
 9

910


Process finished with exit code 0
  • 多次运行可能产生混乱。这种场景就是适合使用锁的场景。
import threading
import time

gl_num = 0

lock = threading.RLock()


# 调用acquire([timeout])时,线程将一直阻塞,
# 直到获得锁定或者直到timeout秒后(timeout参数可选)。
# 返回是否获得锁。
def Func():
    lock.acquire()
    global gl_num
    gl_num += 1
    time.sleep(1)
    print gl_num
    lock.release()


for i in range(10):
    t = threading.Thread(target=Func)
    t.start()
  • 如果一个资源,他对于多线程来说,不用加锁也不会引起任何问题,称为线程安全
  • 线程不安全类型:list,set,dist

生产者消费者问题

import threading
import time
import queue


class Producer(threading.Thread):
    def run(self):
        global queue
        count = 0
        while True:
            # qsize返回queue内容长度
            if queue.qsize() < 1000:
                for i in range(100):
                    count = count +1
                    msg = '生成产品'+str(count)
                    # put是网queue中放入一个值
                    queue.put(msg)
                    print(msg)
            time.sleep(0.5)


class Consumer(threading.Thread):
    def run(self):
        global queue
        while True:
            if queue.qsize() > 100:
                for i in range(3):
                    # get是从queue中取出一个值
                    msg = self.name + '消费了 '+queue.get()
                    print(msg)
            time.sleep(1)


if __name__ == '__main__':
    queue = queue.Queue()

    for i in range(500):
        queue.put('初始产品'+str(i))
    for i in range(2):
        p = Producer()
        p.start()
    for i in range(5):
        c = Consumer()
        c.start()
  • queue是一个用来存放变量的数据结构,特点是先进先出,内部元素排队,可以理解为形成一个特殊的list

多进程

  • 进程间通讯,进程间无任何共享状态
  • 导入multiprocessing
    -父进程死了,子进程如果还在运行则会报错

import multiprocessing
from time import sleep, ctime


def clock(interval):
    while True:
        print("The time is %s" % ctime())
        sleep(interval)



if __name__ == '__main__':
    p = multiprocessing.Process(target = clock, args = (5,))
    p.start()

    while True:
        print('sleeping.......')
        sleep(1)

# 这里利用两个死循环避免了这种情况,但是仍然会有问题
利用子进程


import multiprocessing
from time import sleep, ctime


class ClockProcess(multiprocessing.Process):
    '''
    两个函数比较重要
    1. init构造函数
    2. run
    '''

    def __init__(self, interval):
        super().__init__()
        self.interval = interval

    def run(self):
        while True:
            print("The time is %s" % ctime())
            sleep(self.interval)


if __name__ == '__main__':
    p = ClockProcess(3)
    p.start()

    while True:
        print('sleeping.......')
        sleep(1)

协程

迭代器

  • 可迭代:直接作用于for循环变量
  • 迭代器:不但可以作用于for,还可以被next
  • list不可以被next,所以它不是一个迭代器
  • isinstance,可以判断,利用collections包里面的Iterable模块进行判断

# isinstance案例
# 判断某个变量是否是一个实例

# 判断是否课可迭代
from collections import Iterable
ll = [1,2,3,4,5]

print(isinstance(ll, Iterable))

from collections import Iterator
print(isinstance(ll, Iterator))
  • 强制转换为迭代器
# iter函数

s = 'i love wangxiaojign'

print(isinstance(s, Iterable))
print(isinstance(s, Iterator))

s_iter = iter(s)
print(isinstance(s_iter, Iterable))
print(isinstance(s_iter, Iterator))

生成器

  • 时间换空间
  • generator:一边循环,一边遍历
  • 条件
    • 每次调用都生产出for循环的下一个元素
    • 最后能爆出stopIteration异常
    • 可以被next使用
  • 如果一个函数里面包括了yield,函数就是生成器
  • next调用函数,遇到yield返回
  • 下一次再用next则会从上一次的yield处向后继续执行
# 生成器的案例
# 在函数odd中,yield负责返回
def odd():
    print("Step 1")
    yield 1
    print("Step 2")
    yield 2
    print("Step 3")
    yield 3
 
# odd() 是调用生成器
g = odd()
one = next(g)
print(one)

two = next(g)
print(two)

three = next(g)
print(three)

协程(推荐多进程+协程)

相对于多线程,协程有很大优势,主要第一点是执行效率高,因为子程序切换而不是线程切换,而且是程序自己控制,而多线程具有顺序不定性,并且协程没有线程切换的开关,和多线程比,线程数量越多,协程优势越明显;二是不需要锁机制gil,全局解释器存在被有效利用,协程只有一个线程,不存在变量冲突,在程序中只需要判断状态就好

  • 包:asyncio,tornado,gevent等
  • 定义:协程是为非抢占式多任务产生子程序的计算机控制组件,协程允许不同入口点在不同位置暂停或者开始执行程序
# 协程代码案例1
def simple_coroutine():
    print('-> start')
    x = yield
    print('-> recived', x)

#主线程
sc = simple_coroutine()
print(1111)
# 可以使用sc.send(None),效果一样
next(sc) #预激

print(2222)
sc.send('zhexiao')#主线程向协程发送信号
  • 协程的实现:
    1. yield返回
    2. send调用
  • 协程的四个状态
    1. inspect.getgeneratorstate(…) 2. 2. 2. 函数确定,该函数会返回下述字符串中的一个:
    2. GEN_CREATED:等待开始执行
      GEN_RUNNING:解释器正在执行
      GEN_SUSPENED:在yield表达式处暂停
      GEN_CLOSED:执行结束
      next预激(prime)
  • yield from
    调用协程为了得到返回值,协程必须正常终止
    生成器正常终止会发出StopIteration异常,异常对象的vlaue属性保存返回值
    yield from从内部捕获StopIteration异常
  • 使用生成器直接作为参数
# 案例v03
def gen():
    for c in 'AB':
        yield c
# list直接用生成器作为参数
print(list(gen()))

def gen_new():
    yield from 'AB'

print(list(gen_new()))
  • 委派生成器:包含yield from表达式的生成器函数
  • 委派生成器:在yieldfrom表达式出暂停,调用方可以直接把数据发给子生成器
  • 子生成器在把产出的值发给调用放,生成器在最后,解释器会抛出StopIteration,并且把返回值附加到异常对象上
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值