简述进程,线程和协程

线程,进程和协程

线程

线程的概念

并发

任务数大于cpu核载,通过系统的各种任务跳读算法,是任务“在一起”执行任务! 假的多任务

并行

任务数小于cpu核数,即任务真的在一起执行

多线程

1 同时执行

下面例子中test1和test2是同时执行

import threading
import time

def tes1():
    for i in range(3):
        print("--test1--%d" % i)
        time.sleep(1)
        
def tes2():
    for i in range(3):
        print("--test2--%d" % i)
        time.sleep(1)
        
if __name__ == "__main__":
    t1 = threading.Thread(target=test1)
    t2 = threading.Thread(target=test2)
    
    t1.start()  # 启动线程,让线程开始执行
    t2.start()
# 执行结果
--test1--0
--test2--0
(间隔1秒)
--test1--1
--test2--1
(间隔1秒)
--test1--2
--test2--2

2 顺序执行

test1先执行,test2后执行

import threading
import time

def tes1():
    for i in range(3):
        print("--test1--%d" % i)
        
def tes2():
    for i in range(3):
        print("--test2--%d" % i)
        
if __name__ == "__main__":
    t1 = threading.Thread(target=test1)
    t2 = threading.Thread(target=test2)
    
    t1.start()
    time.sleep(1)
    print("test1 is over")
    
    t2.start()
    time.sleep(1)
    print("test2 is over")
    
# 执行结果
--test1--0
--test1--1
--test1--2
(间隔1秒)
test1 over
--test2--0
--test2--1
--test2--2
(间隔1面)
test2 over

多线程全局变量

全局变量

import threading
import time

g_num = 100

def test1():
    global g_num
    g_num += 1
    print("--in test1 g_num=%d" % g_num)
    
def test2():
    print("--in test2 g_num=%d" % g_num)
    
if __name__ == "__main__":
    t1 = threading.Thread(target=test1)
    t2 = threading.Thread(target=test2)
    
    t1.start()
    time.sleep(1)
    
    t2.start()+
    time.sleep(1)

去函数那个笔记了解全局变量

多线程全局变量实参

import threading
import time

g_num = 100

def test1(temp): #传递实参的方式
    temp.append(33)
    print("--in test1 temp=%s" % str(temp))
    545.
def test2():
    print("--in test2 temp=%s" % str(temp))
g_num = [11,22]

if __name__ == "__main__":
    # 创建线程
    t1 = threading.Thread(target=test1, args=(g_num,)) 
    t2 = threading.Thread(target=test2, args=(g_num,))
    
    t1.start()
    time.sleep(1)
    
    t2.start()
    time.sleep(1)

多线程共享全局变量资源竞争

import threading
import time

g_num = 0

def test1():
    glibal g_num
    for i in range(num):
        g_num += 1
    print("--in test1 g_num=%d" % g_num)
    
def test2():
    glibal g_num
    for i in range(num):
        g_num += 1
    print("--in test2 g_num=%d" % g_num)
    
if __name__ == "__main__":
    # 创建线程
    t1 = threading.Thread(target=test1,args=(100,))
    t2 = threading.Thread(target=test2,args=(100,))
    
    # t1 = threading.Thread(target=test1,args=(10000,))
    # t2 = threading.Thread(target=test2,args=(10000,))
    
    t1.start()
    t2.start()

多线程解决全局变量资源竞争01

使用互斥锁

import threading
import time

g_num = 0

def test1():
    glibal g_num
    # 上锁,如果之前没有上锁,那么此时上锁成功
    # 如果之前上过锁,那么就会堵塞在这里,知道这个锁解开为止
    muext.acquire()
    for i in range(num):
        g_num += 1    
    # 解锁
    muext.release()  
    print("--in test1 g_num=%d" % g_num)
                                   
def test2():
    glibal g_num
    muext.acquire()
    for i in range(num):
        g_num += 1
    muext.release()
    print("--in test2 g_num=%d" % g_num)
# 建立一个互斥锁,默认是没有上锁的
muext = threading.Lock()    

if __name__ == "__main__":
    
    t1 = threading.Thread(target=test1,args=(10000,))
    t2 = threading.Thread(target=test2,args=(10000,))
    
    t1.start()
    t2.start()
# 上述代码上锁和解锁在for循环外,解释:子线程t1和t2不知道谁先执行,假如是t1先执行,在上锁的后,一直执行for循环,知道循环结束为止然后在解锁,在执行t2此时全局变量从0变成了10000,直到for循环结束解锁变成2000,程序解锁

多线程解决全局变量资源竞争02

import threading
import time

g_num = 0

def test1():
    glibal g_num
    for i in range(num):
        muext.acquire()
        g_num += 1
        muext.release()
    print("--in test1 g_num=%d" % g_num)
    
def test2():
    glibal g_num
    for i in range(num):
        muext.acquire()
        g_num += 1
        muext.release()
    print("--in test2 g_num=%d" % g_num)

muext = threading.Lock()

if __name__ == "__main__":

    t1 = threading.Thread(target=test1,args=(10000,))
    t2 = threading.Thread(target=test2,args=(10000,))
    
    t1.start()
    t2.start()
# 如果上锁和解锁在for循环内部,不管t1和t2谁先执行,每次执行+1结束后解锁,然后在分配t1和t2谁先执行,这个先后是没有规律的,可能是t1执行很多次之后再是执行t2,可能反之,所以其中一个是for循环执行结束后得到20000,但是另外一个一定是执行10000后还在叠加

多任务版UDP聊天

import socket
import threading

if __name__ == "__main__":
    # 创建套接字
    udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    # 绑定端口
    udp_socket.bind(("", 7788))
    # 获得对方ip和port
    dest_ip = input("请输入ip:")
    dest_port = input("请输入port:")
    # 接受数据
    while True:
        recv_data = udp_socket.recvfrom(1024)
        print(recv_data)
        
    # 发送数据
    while True:
        send_data = input("输入数据:")
        udp_socket.sendto(send_data.encode("utf-8"), dest_ip,dest_port)
        
# 利用多线程

import socket
import threading

def recv_msg(udp_socket):
    # 接受数据
    while True:
        recv_data = udp_socket.recvfrom(1024)
        #print(recv_data)
        print("[%s]:%s" %(recv_data[1], str(recv_data[0].decode("utf-8"))))
        # 显示发送的对方地址和信息

def send_msg(udp_socket,dest_ip,dest_port):
    # 发送数据
    while True:
        send_data = input("输入数据:")
        udp_socket.sendto(send_data.encode("utf-8"), dest_ip,dest_port)


if __name__ == "__main__":
    # 创建套接字
    udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    # 绑定端口
    udp_socket.bind(("", 7788))
    # 获得对方ip和port
    dest_ip = input("请输入ip:")
    dest_port = int(input("请输入port:"))  # 注意
    
    t_recv = threading.Thread(target=recv_msg, args=(udp_socket,))
    t_send = threading.Thread(target=send_msg, args=(udp_socket,dest_ip,dest_port))
    
    t_recv.start()
    t_send.start()

进程

进程的概念

一个程序运行起来,代码+用到的资源称之为进程,他是操作系统分配资源的基本单位元

导入multiprocessing模块

子进程的传递参数

import multiprocessing
# import os  # 导入路径模块
import time

def test(a,b,c,*args, **kwargs):
    print(a)
    print(b)
    print(c)
    print(args)  # 拆包(元组)
    print(kwargs)  # 拆包(字典)

if __name__ == "__main__":
    p = multiprocessing.Process(target=test, args=(1,2,3,4,5,6,7,8), kwargs={"name":"Lily"})
    # 当导入元素和元组的时候,统一以元组的形式导入
    # 当传入kwargs的时候,在创建p类的时候,传入字典形式
    
    p.start()

多进程之间不共享全局变量

进程之间是两个独立的程序不共享全局变量

import multiprocessing
import time

nums = [1,2,3]  #设定全局变量

def test1():
    nums.append(4)  # 利用方法改变全局变量
    print("在test1中nums=%s" % str(nums))
    
def test2():
    print("在test2中nums=%s" % str(nums))
    
if __name__ == "__main__":
    # 创建进程
    p1 = multiprocessing.Proess(target=test1)
    p2 = multiprocessing.Proess(target=test2)
    
    p1.start()
    p1.join()  # 确保p1在执行结束后再执行p2
    time.sleep(1)  # 或者是在p1执行后停顿1秒
    p2.start()
    
# 得出结果是test1中[1,2,3,4], test2中[1,2,3]
# 在进程中不共享全局变量

进程和线程的区别

简单的对比

进程:能够完成多任务,例如一台电脑上可以运行多个QQ

(进程是系统进行资源的分配和调度的一个独立单位)

线程:能够完成多任务,例如在同一个QQ可以开多个聊天窗口

(线程属于进程,是进程的一个实体,是cpu调度和分配的基本单位,能更小的独立运行的基本单位,线程自己使用系统资源)

区别

1 一个程序至少有一个进程,一个进程至少有一个线程

2 线程的划分尺度小于进程(就是占用资源比进程少)

3 进程在执行过程中拥有独立的内存单元,而多个线程共享内存,提高工作效率

4 线程不能独立的运行,必须依存在进程中

5 进程就好比工厂的流水线,线程就好比流水线的员工

进程之间的通信-Queue队列

实例(仅限一台电脑或是服务器中的两个进程之间的数据共享)

import multiprocessing
# 一个进程向Queue中写入数据,另一个进程从Queue获取数据
# 通过Queue完成了多歌需要配合进程间的数据共享

def down(q):
    # 下载数据
    # 模拟在网上下载数据,就简单的创建下载好的列表
    data = [1,2,3,4]
    
    # 向队列写入数据
    for temp in data:
        q.put(temp)
    print("--数据已经存到队列中---")
    
def fenxi(q):
    # 数据处理
    fenxi_data = list()  #或者是fenxi_list = []
    
    # 向保存好的队列中获取数据
    while True:
        new_data = q.get()  # new_data是获取的数据
        fenxi_data.append(new_data)
        
        # 判断:如果队被取空的话,就退出
        if q.empty():
            break

if __name__ == "__main__":
    # 创建一个队列
    q = multiprocessing.Queue()
    
    # 创建进程  # 传入实参队列在创建的子进程中调用q
    p1 = multiprocessing.Process(target=down, args=(q,))
    p2 = muultiprocessing.Proess(target=fenxi, args=(q,))
    
    p1.start()
    # p1.join()
    p2.start()

# 以上操作有问题,创建的子进程p1和p2,不能确定那个子进程先运行,会导致p1还没有下载好,p2就直接获取数据,所以在p1.start()后添加一个p1.join(), p1.join()的功能就是让p1执行完之后再执行其他的进程

补充说明

初始化Queue()对象时(例如q=Queue()),有下列方法

q.put() # 向队列导入写入数据

q.get() # 向队列下载获取数据

q.empty() # 如果队列是空的,返回True, 反之False

q.full() # 如果列队满了,返回True,反之False

进程池

进程之间的通信

进程间通信就是在不同进程之间传播或交换信息,那么不同进程之间存在着什么双方都可以访问的介质呢?进程的用户空间是互相独立的,一般而言是不能互相访问的,唯一的例外是共享内存区。但是,系统空间却是“公共场所”,所以内核显然可以提供这样的条件。除此以外,那就是双方都可以访问的外设了。在这个意义上,两个进程当然也可以通过磁盘上的普通文件交换信息,或者通过“注册表”或其它数据库中的某些表项和记录交换信息。广义上这也是进程间通信的手段,但是一般都不把这算作“进程间通信”。因为那些通信手段的效率太低了,而人们对进程间通信的要求是要有一定的实时性。

进程间通信主要包括管道, 系统IPC(包括消息队列,信号量,共享存储), SOCKET.

进程和线程区别

定义的区别:进程是系统进行资源分配和调度的一个独立的单位;线程是进程的实体,是cpu调度和分配的基本单位

一个程序至少有一个进程,一个进程至少有一个线程;

线程的划分尺度小于进程(资源比进程少),使得多线程程序的并发性高。

进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率

线线程不能够独立执行,必须依存在进程中

可以将进程理解为工厂中的一条流水线,而其中的线程就是这个流水线上的工人

优缺点:线程执行开销小,效率高,,但不利于资源的管理和保护;而进程正相反。

协程

协程概念

用到更少的资源,:在一个线程中的某个函数,可以在任何地方保存当前函数的一 些临时变量等信息,然后切换到另外一个函数中执行,注意不是通过调用函 数的方式做到的,并且切换的次数以及什么时候再切换到原来的函数都由开 发者自己确定

协程和线程的区别

在实现多任务时, 线程切换从系统层面远不止保存和恢复 CPU上下文这么简 单。 操作系统为了程序运行的高效性每个线程都有自己缓存Cache等等数 据,操作系统还会帮你做这些数据的恢复操作。 所以线程的切换非常耗性 能。但是协程的切换只是单纯的操作CPU的上下,所以⼀秒钟切换个上百 万次系统都抗的住。

如何实现协程

1 生成器实现简单的协程

import time 

def f1():
    while True:
        print("---1----")
        yield
        time.sleep(1)

def f2():
    while True:
        print("---2----")
        yield
        time.sleep(1)
        
if __name__ == "__main__":
    ff1 = f1()
    ff2 = f2()
    while True:
        next(ff1)
        next(ff2)

2 greenlet实现协程 (了解)

from greenlet import greenlet
import time

def w1():
    while True:
        print("---1---")
        ww1.switch()  # 使用greenlet模块中swtich()方法
        time.sleep(1)
        
def w2():
    while True:
        print("---1---")
        ww2.switch()  # 使用greenlet模块中swtich()方法
        time.sleep(1)
        
if __name__ == "__main__":
    ww1 = greenlet(w1)
    ww2 = greenlet(w2)
    ww1.switch()

3 gevent实现协程(重要)

import gevent

def f(n):
    for i in range(n):
        print(gevent.getcurrent(), i)

g1 = gevent.spawn(f,5)
g2 = gevent.spawn(f,5)
g3 = gevent.spawn(f,5)

g1.join()
g2.join()
g3.join()

# 执行结果
<Greenlet "Greenlet-0" at 0x7f9e6251a748: f(5)> 0
<Greenlet "Greenlet-0" at 0x7f9e6251a748: f(5)> 1
<Greenlet "Greenlet-0" at 0x7f9e6251a748: f(5)> 2
<Greenlet "Greenlet-0" at 0x7f9e6251a748: f(5)> 3
<Greenlet "Greenlet-0" at 0x7f9e6251a748: f(5)> 4
<Greenlet "Greenlet-1" at 0x7f9e6251a948: f(5)> 0
<Greenlet "Greenlet-1" at 0x7f9e6251a948: f(5)> 1
<Greenlet "Greenlet-1" at 0x7f9e6251a948: f(5)> 2
<Greenlet "Greenlet-1" at 0x7f9e6251a948: f(5)> 3
<Greenlet "Greenlet-1" at 0x7f9e6251a948: f(5)> 4
<Greenlet "Greenlet-2" at 0x7f9e6251aa48: f(5)> 0
<Greenlet "Greenlet-2" at 0x7f9e6251aa48: f(5)> 1
<Greenlet "Greenlet-2" at 0x7f9e6251aa48: f(5)> 2
<Greenlet "Greenlet-2" at 0x7f9e6251aa48: f(5)> 3
<Greenlet "Greenlet-2" at 0x7f9e6251aa48: f(5)> 4



# 达成任务切换
import gevent

def f(n):
    for i in range(n):
        print(gevent.getcurrent(), i)
        # 用来模拟一个耗时操作,不是用time模块
        gevent.sleep(1)

g1 = gevent.spawn(f,5)
g2 = gevent.spawn(f,5)
g3 = gevent.spawn(f,5)

g1.join()
g2.join()
g3.join()

# 执行结果
<Greenlet "Greenlet-0" at 0x7fc93b84d748: f(5)> 0
<Greenlet "Greenlet-1" at 0x7fc93b84d948: f(5)> 0
<Greenlet "Greenlet-2" at 0x7fc93b84da48: f(5)> 0
<Greenlet "Greenlet-0" at 0x7fc93b84d748: f(5)> 1
<Greenlet "Greenlet-1" at 0x7fc93b84d948: f(5)> 1
<Greenlet "Greenlet-2" at 0x7fc93b84da48: f(5)> 1
<Greenlet "Greenlet-0" at 0x7fc93b84d748: f(5)> 2
<Greenlet "Greenlet-1" at 0x7fc93b84d948: f(5)> 2
<Greenlet "Greenlet-2" at 0x7fc93b84da48: f(5)> 2
<Greenlet "Greenlet-0" at 0x7fc93b84d748: f(5)> 3
<Greenlet "Greenlet-1" at 0x7fc93b84d948: f(5)> 3
<Greenlet "Greenlet-2" at 0x7fc93b84da48: f(5)> 3
<Greenlet "Greenlet-0" at 0x7fc93b84d748: f(5)> 4
<Greenlet "Greenlet-1" at 0x7fc93b84d948: f(5)> 4
<Greenlet "Greenlet-2" at 0x7fc93b84da48: f(5)> 4

进程,线程和协程的对比(面试重点)

A 进程是资源分配的单位

B 线程是操作系统调度的单位

C 进程切换需要的资源最大,效率低

D 线程切换需要的资源一般,效率也很一般

E 协程切换任务资源小,效率高

F 多进程,多线程根据cpu的核数不一样可能并行,但是协程是在一个线程中,所以是并发的

G 进程不共享资源,线程共享资源

GIL(全局解释器锁)(面试重点)

每个线程在执行过程中都需要先获取GIL,保证同一时刻只有一个线程可以执行代码,所以线程是并发的,都是讲并发运行成串行,由此来控制同一时间内共享数据只能被一个任务修改,进而保证数据的安全!

底层知识

因为python的线程是调用操作系统的原生线程,这个原生线程就是C语言写的原生线程。因为python是用C写的,启动的时候就是调用的C语言的接口。因为启动的C语言的远程线程,那它要调这个线程去执行任务就必须知道上下文,所以python要去调C语言的接口的线程,必须要把这个上限问关系传给python,那就变成了一个在加减的时候要让程序串行才能一次计算。就是先让线程1,再让线程2.......

多线程用于IO密集型,如socket,爬虫,web

多进程用于计算密集型,如金融分析

互斥锁(面试重点)

当多个线程几乎同时修改一个共享数据的时候,需要进行同步控制,线程同步能够保证多个线程安全的竞争资源,最简单的同步机制就是引入互斥锁

如何运行:某个线程需要更改共享数据的时候,先锁定,此时资源状态为锁定状态,其他线程不能更改,直到该线程释放资源,将资源的状态变成非锁定状态,其他线程才能再次锁定该资源,互斥锁保证每次只有一个线程进行操作,从而保证多线程情况的数据正确性!

优点:确保某段关键代码只能有一个线程从头到尾完整的执行

缺点:A--阻止了多线程的并发,包含锁的某段代码只能以单线程的模式执行,效率大打折扣。B--由于可以存在多个锁,不同的线程持有不同的锁,并试图获取对方的锁,可能会造成死锁!

posted @ 2018-12-27 19:49  Pythoner码农  阅读(368)  评论(0编辑  收藏  举报