python线程、进程、协程

一、线程

有时被称为轻量级进程,是程序执行流的最小单元。一个标准的线程由线程ID、当前指令指针(PC)、寄存器集合和堆栈组成。是被系统独立调度和和分派的基本单位。线程不独立拥有系统资源,但是它可以与同属一个进程的其他线程共享该进程资源。同一个进程的多线程之间可以并发执行。
线程也有就绪、阻塞和运行三种基本状态。
就绪状态是指线程具备运行的条件,逻辑上可以运行,在等待处理机;
运行状态是指线程占有处理机正在运行;
阻塞状态是指线程在等待一个事件(如信号量),逻辑上不可执行。
每一个应用程序中都至少一个线程和一个进程。在单个程序中同时运行多个线程完成不同的工作,叫做多线程。

普通的多线程:

import threading
import time

def foo(num):
    time.sleep(1)
    print("now is number {}".format(num))


for i in range(10):
    t = threading.Thread(target=foo, args=(i,))
    t.start()
print("main thread")
----------------------------------------------------------------------------
start       线程准备就绪,等待CPU调度
setName     为线程设置名称
getName     获取线程的名称
setDaemon   设置为守护线程。即主线程是否等待子线程执行完毕。
join        让程序变为串行的

自定义线程类:

import threading
class MyThreading(threading.Thread):
    def __init__(self, *args, **kwargs):
        super(MyThreading, self).__init__(*args, **kwargs)
    
    def run(self):
        print("now is number {}".format(self.num))

for i in range(10):
    t = MyThreading(num=i)
    t.start()

锁的概念是:限制某一时刻只有一个线程能访问某个指定的数据

锁一共有五种:

1.Lock 普通锁,不支持嵌套
2.RLock 普通锁,支持嵌套
3.BoundedSemaphore 信号量
4.Event 事件
5.Condition 条件

1. 普通锁

普通锁也叫互斥锁,是独占的,同一时刻只有一个线程被执行。
包括Lock和RLock

import time
import threading

num = 10
def foo(lock):
    global num
    lock.acquire()
    num -= 1
    time.sleep(1)
    print(num)
    lock.release()

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

2. 信号量

类名:BoundedSemaphore
这种锁允许一定数量的线程同时更改数据,他不是互斥锁。比如地铁安检,排队人很多,工作人员只允许一定数量的人进入安检区,其它的人继续排队。

import threading
import time

def foo(i, semaphore):
    semaphore.acquire() # 上锁
    print(i)
    time.sleep(1)
    semaphore.release() #释放锁

semaphore = threading.BoundedSemaphore(5) #最多允许5个线程同时执行
for i in range(10):
    t = threading.Thread(target=foo, args=(i, semaphore))
    t.start()

3. 事件

类名:Event
主要提供了三个方法,
event = threading.Event()
event.set()
event.clear()
event.wait()
事件机制:全局定义了一个Flag,如果Flag为False的话,那么当程序执行wait时就会阻塞,如果Flag为True,执行wait时就不再阻塞。类似交通红绿灯
event.clear()  ---将Flag设置False
event.set()  ---将Flag设置True

import threading
import time

'''
        线程的event
'''
event = threading.Event()


def lighter():
    counter = 0
    event.set()    # 设置标志位,表示现在是绿灯,可以通行
    while True:
        if counter >= 5 and counter < 10:
            event.clear()    #清除标志位,表示红灯亮起,禁止通行
            print("\033[41;1m now is red light,please waiting......\033[0m")
        elif counter > 10:
            event.set()      # 重设标志位,并且使计数器重设成0
            counter = 0
        else:
            print("\033[42;1m now is green light,start running......\033[0m")
        time.sleep(1)
        counter += 1


def car(name):
    while True:
        if event.is_set():
            print("[%s] is running" % name)
            time.sleep(1)
        else:
            print("now ,Please waiting to green light [%s]" % name)
            event.wait()
            print("becoming green,you can running [%s]" % name)


light = threading.Thread(target=lighter,)
light.start()

cars = threading.Thread(target=car, args=("Tesla",))
cars.start()

4. 条件

类名:Condition
该机制会使线程等待,只有满足某条件时,才释放n个线程

import threading, time

def run(n):
    con.acquire()
    con.wait()
    print("run the thread: %s" %n)
    con.release()

if __name__ == '__main__':

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

    while True:
        inp = input('>>>')
        if inp == "q":
            break
        # 下面这三行是固定语法
        con.acquire()
        con.notify(int(inp))  # 这个方法接收一个整数,表示让多少个线程通过
        con.release()
        time.sleep(0.5)

二、队列

队列是一种先进先出的数据结构,与之对应的是堆栈这种后进先出的结构。python内置了一个queue模块,包含队列如下:
1.queue.Queue(): 先进先出队列
2.queue.LifoQueue(): 后进先出队列
3.queue.PriorityQueue(): 优先级队列
4.queue.deque(): 双向队列


(1)先进先出队列--queue.Queue(5) 
import queue

q = queue.Queue(5)  # 5是指定队列的大小,为空的话队列无限大
for i in range(5):
    q.put(i)
    print(q.get())

队列的一些方法:
q.qsize()  ---获取当前队列中元素的个数,也就是当前队列的大小
q.empty()  ---判断当前队列是否为空,返回True或False
q.full()  ---判断当前队列是否已满,返回True或False
q.put(self, block=False, timeout=None)  ---往队列里放入一个元素,默认是不阻塞和无时间限制的。
q.get(self, block=False, timeout=None)  ---在队列里取一个元素,参数和放入是一样的。
q.join()  ---阻塞进程,直到所有任务完成,需配合task_done方法使用。
q.task_done()  ---表示某个任务完成。每一条get语句后需要一条task_done

(2)后进先出队列--queue.LifoQueue()

import queue
q = queue.LifoQueue()
q.put(123)
q.put(456)
print(q.get())
>>>>>>>>>>>>>>获得456

(3)优先级队列--queue.PriorityQueue()
带有权重的队列,每个元素都是一个元组,前面的数字表示它的优先级,数字越小优先级越高,同样的优先级先进先出。

import queue

q = queue.PriorityQueue()

q.put((1, 'alex'))
q.put((12, 'alice'))
q.put((1, 'jack'))
q.put((13, 'eric'))
print(q.get())
print(q.get())
print(q.get())
>>>>结果是(1, 'alex')  (1, 'jack') (12, 'alice')
获取其中的元素就是和列表一样,用索引来获取

(4)双向队列--queue.deque()
import queue
q = queue.deque()
q.append(123)
q.append(333)
q.appendleft(456)
q.pop()
q.popleft()

三、进程

在python中multiprocessing模块提供了Process类,实现进程相关的功能。但是他是基于fork机制的,因此不被windows平台支持,想要在windows中运行,必须使用if __name__ == '__main__:的方式,显然这只能用于调试和学习,不能用于实际环境。
(PS:在这里我必须吐槽一下python的包、模块和类的组织结构。在multiprocess中你既可以import大写的Process,也可以import小写的process,这两者是完全不同的东西。这种情况在python中很多,新手容易傻傻分不清。)
一个简单的进程的栗子:
from multiprocessing import Process

def foo(i):
    print("This is Process ", i)

if __name__ == '__main__':
    for i in range(5):
        p = Process(target=foo, args=(i,))
        p.start()
用法和线程其实差不多。
(3.1)进程的数据共享
from multiprocessing import Process

temp_list = []
def foo(i):
    temp_list.append(i)
    print("This is Process {},list is :{}".format(i, temp_list))


if __name__ == '__main__':
    for i in range(5):
        p = Process(target=foo, args=(i,))
        p.start()
结果是:

可以发现进程间的数据是相互独立的,在各个进程中只有自己的数据,完全不发共享。若要进程间共享资源可以用queues/Array/Manager这三个multiprocess模块提供的类。

(3.2)使用Array共享数据
    

from multiprocessing import Process
from multiprocessing import Array

def Foo(i,temp):
    temp[0] += 100
    for item in temp:
        print(i,'----->',item)

if __name__ == '__main__':
    temp = Array('i', [11, 22, 33, 44])
    for i in range(2):
        p = Process(target=Foo, args=(i,temp))
        p.start()

每一次的数据都是在上一次的基础上进行叠加

(3.3)使用Manager共享数据

from multiprocessing import Process,Manager

def Foo(i,dic):
    dic[i] = 100+i
    print(dic.values())

if __name__ == '__main__':
    manage = Manager()
    dic = manage.dict()
    for i in range(10):
        p = Process(target=Foo, args=(i,dic))
        p.start()
        p.join()

Manager比Array要好用一点,因为它可以同时保存多种类型的数据格式      

四、协程

线程和进程的操作是由程序触发系统接口,最后的执行者是系统,它本质上是操作系统提供的功能。
而协程的操作则是程序员指定的,在python中通过yield,人为的实现并发处理。
协程存在的意义:对于多线程应用,CPU通过切片的方式来切换线程间的执行,线程切换时需要耗时。协程,则只使用一个线程,分解一个线程成为多个“微线程”,在一个线程中规定某个代码块的执行顺序。
协程的适用场景:当程序中存在大量不需要CPU的操作时(IO)

栗子:
from gevent import monkey; 
import gevent
import requests

monkey.patch_all()
def f(url):
    print('GET: %s' % url)
    resp = requests.get(url)
    data = resp.text
    print('%d bytes received from %s.' % (len(data), url))

gevent.joinall([
        gevent.spawn(f, 'https://www.python.org/'),
        gevent.spawn(f, 'https://www.yahoo.com/'),
        gevent.spawn(f, 'https://github.com/'),
])

进程、线程和协程的区别

python中进程和线程的最大区别在于稳定性和效率问题。
多进程之间互不影响,一个崩溃了不影响其他进程,稳定性较高
多线程由于在同一个进程里,一个线程崩溃整个进程也会崩溃。

多进程对系统资源开销大,多线程对系统资源开销小,这方面多线程占一点优势。

协程最大的优势是高执行效率,因为子程序切换不是线程切换,没有切换线程的开销,不需要多线程锁。

区别:
(1)调度:线程作为调度和分配的基本单位,进程作为拥有资源的基本单位

(2)并发性:不仅进程之间可以并发执行,同一个进程的多个线程之间也可并发执行

(3)拥有资源:进程是拥有资源的一个独立单位,线程不拥有系统资源,但可以访问隶属于进程的资源.

(4)系统开销:在创建或撤消进程时,由于系统都要为之分配和回收资源,导致系统的开销明显大于创建或撤消线程时的开销。

猜你喜欢

转载自www.cnblogs.com/crazy-xf/p/9774503.html