简述进程,线程和协程

线程,进程和协程

线程

线程的概念

并发

任务数大于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--由于可以存在多个锁,不同的线程持有不同的锁,并试图获取对方的锁,可能会造成死锁!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,445评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,889评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,047评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,760评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,745评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,638评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,011评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,669评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,923评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,655评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,740评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,406评论 4 320
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,995评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,961评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,023评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,483评论 2 342

推荐阅读更多精彩内容

  • python之进程、线程与协程 有这么个例子说他们的区别,帮助理解很有用。 有一个老板想开一个工厂生产手机。 他需...
    道无虚阅读 3,171评论 0 3
  • 1. 基础知识 1.1、 基本概念、 功能 冯诺伊曼体系结构1、计算机处理的数据和指令一律用二进制数表示2、顺序执...
    yunpiao阅读 5,246评论 1 22
  • 必备的理论基础 1.操作系统作用: 隐藏丑陋复杂的硬件接口,提供良好的抽象接口。 管理调度进程,并将多个进程对硬件...
    drfung阅读 3,525评论 0 5
  • 又来到了一个老生常谈的问题,应用层软件开发的程序员要不要了解和深入学习操作系统呢? 今天就这个问题开始,来谈谈操...
    tangsl阅读 4,088评论 0 23
  • 使用pl/sql创建存储过程,具体创建代码已经写过,这里不再赘述。 具体创建过程如下: 找到Procedures,...
    乐百事52淑熙阅读 562评论 0 1