zoukankan      html  css  js  c++  java
  • 进程与线程

     
     
     
     

    进程与线程

    进程

    进程的创建

    1. 和线程使用方式基本一样,只不过导入的模块不同
    2. 导入multiprocessing模块
    3. 通过multiprocessing.Process(target=work1)指定执行的函数,然后start
    4. 主进程结束后,子进程不会退出

     进程可以实现多任务(主进程结束之后,不会结束子进程,不过会有特殊情况)

    import multiprocessing


    def doing1():
    while True:
    print("doing1")


    def doing2():
    while True:
    print("doing2")


    def main():
    # 创建进程1
    th_name1 = multiprocessing.Process(target=doing1)
    th_name1.start()
    # 创建进程2
    th_name2 = multiprocessing.Process(target=doing2)
    th_name2.start()


    if __name__ == '__main__':
    main()

    进程之间不共享全局变量 

    1. 进程和线程在共享全局变量上面是有区别的
    2. 进程之间不会共享全局变量
    3. 进程开启后,相当于把当前整个代码复制一份,所以这个子进程是知道num之前的值的
    import multiprocessing
    import time

    num = [11, 12]


    def doing1():
    for i in range(3):
    num.append(i)
    print("doing1=%s" %num)

    def doing2():
    print(num)


    def main():
    p1 = multiprocessing.Process(target=doing1)
    p1.start()
    time.sleep(1)
    p2 = multiprocessing.Process(target=doing2)
    p2.start()


    if __name__ == '__main__':
    main()

    子进程传递参数

    子进程也是可以传参数的

    利用args或者kwargs可以传递参数,并且args里面传的是元祖

    import multiprocessing


    def work(num):
    for i in range(num):
    print("输出")


    # 进程需要在main中执行
    # 执行3次
    def main():
    p = multiprocessing.Process(target=work, args=(3,))
    # 指定参数执行3次
    # p = multiprocessing.Process(target=work,kwargs={"num":3})
    p.start()


    if __name__ == '__main__':
    main()

    进程和线程的区别

    1. 功能
      1. 进程    能够完成多任务,比如在一台电脑上能够同时运行多个QQ
      2. 线程    能够完成多任务,比如一个QQ中的多个聊天窗口
    2. 定义的不同
      1. 进程是系统进行资源分配和调度的一个独立单位
      2. 线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位,线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源
    3. 区别
      1. 一个程序至少有一个进程,一个进程至少有一个线程
      2. 线程的划分尺度小于进程(资源比进程少),使得多线程程序的并发性搞
      3. 进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大的提高了程序的运行效率
      4. 线程不能够独立执行,必须依存在进程中
    4. 优缺点
      1. 线程执行开销小,单不利于资源的管理和保护
      2. 进程正相反
    5. 进程负责分配资源,线程负责做事情.

    进程间通信

    进程间通信有很多种    Queue(先进先出)、socket、文件等

    基本的使用方式

    # 创建
    q=multiprocessing.Queue([maxsize])
    # 放数据
    q.put(xxx)
    # 取数据
    q.get()

    示例

    import multiprocessing
    import time


    def doing1(q):
    str = "laowang"
    for i in str:
    q.put(i)

    def doing2(q):
    while True:
    result=q.get()
    print(result)
    def main():
    # 创建队列对象
    q=multiprocessing.Queue()
    p1 = multiprocessing.Process(target=doing1,args=(q,))
    p1.start()
    time.sleep(1)
    p2 = multiprocessing.Process(target=doing2,args=(q,))
    p2.start()


    if __name__ == '__main__':
    main()

    queue的参数和函数

    maxsize参数可以指定队列对象最多放多少个数据

    qsize函数可以获取当前对立中数据的个数

    get和put方法都有一个timeout参数,可以防止这种情况

    import multiprocessing

    # 限制存与取3个
    q = multiprocessing.Queue(3)
    q.put(1)
    q.put(2)
    q.put(3)
    print(q.get())
    print(q.get())
    print(q.get())

    进程池

    1. 当需要创建的子进程数量不多时,可以直接利用multprocessing中的Process动态生成多个进程但是如果是上百甚至上千个目标,手动的去创建进程的工作量巨大,此时就可以用到multprocessing模块提供的Pool方法
    2. 初始化Pool时,可以指定一个最大进程数,当有新的请求提交到Pool中时,如果池还没有满,那么就会创建一个新的进程用来执行该请求,单如果池中的进程数已经达到指定的最大值,那么该请求就会等待,直到池中有进程结束,才会用之前的进程来执行新的任务
    import multiprocessing
    import time


    def work():
    print("test")
    time.sleep(1)


    def main():
    # 3个进程
    p = multiprocessing.Pool(3)
    for i in range(10):
    p.apply_async(work)

    time.sleep(5)
    print("over...")


    if __name__ == '__main__':
    main()

    进程池 -close和join函数

    1. 如果主进程使用进程池的子进程,当主进程没有代码执行的时候,那么进程池中的子进程也会跟着直接结束(使用普通的Process创建不会出现这个问题)
    2. close函数表示不再向进程池中添加任务
    3. join表示等待子进程代码执行完毕
    import multiprocessing
    import time

    num = [11, 12]


    def doing1():
    for i in range(3):
    num.append(i)
    print("doing1=%s" % num)


    def main():
    p1 = multiprocessing.Pool(10)
    for i in range(10):
    p1.apply_async(doing1)
    p1.close()
    p1.join()


    if __name__ == '__main__':
    main()

    进程编号-pid

    1. pid,进程编号
    2. 通过导入os模块,使用getpid()函数进行获取    os.getpid()

    进程池间的通信

    1. 进程池间的通信和进程间通信很相似,但又有区别
    2. 进程间通信使用的是multiprocessing.Queue()
    3. 进程池间通信使用的是multiprocessing.Manager().Queue()
    4. 关于Queue对象的用法是一致的
    import multiprocessing


    def doing1(q):
    print(q.get())


    def main():
    # 创建队列对象
    q = multiprocessing.Manager().Queue()
    q.put([11, 22, 33])
    p1 = multiprocessing.Pool(3)
    p1.apply_async(doing1, args=(q,))
    p1.close()
    p1.join()


    if __name__ == '__main__':
    main()

    线程

    线程-锁

    线程-join函数

    多线程的好处是可以共享全局变量,一起操作同一个东西,但是如果操作不好,可能会有问题.

    import threading

    num = 0


    def doing1():
    global num
    for i in range(1000000):
    num += 1
    print("doing1=", num)


    def doing2():
    global num
    for i in range(1000000):
    num += 1
    print("doing2=", num)


    def main():
    th_num1 = threading.Thread(target=doing1)
    th_num2 = threading.Thread(target=doing2)
    th_num1.start()
    # 等待这个线程完事儿之后,再往后执行
    th_num1.join()
    th_num2.start()


    if __name__ == '__main__':
    main()

    虽然问题解决了,但是这个join是一个伪线程,因为需要等待第一个线程执行完毕之后,在执行第二个,意味着跟单线程没什么区别

    进程之间是不会共享全局变量

    互斥锁

    务必注意互斥锁的位置,如果用不好,其实就跟join的效果是一样的,threading模块中定义了Lock类,可以方便的处理锁定:

    import threading

    num = 0
    mutex = None


    def doing1():
    global num
    for i in range(1000000):
    # 在循环内部加锁,就是在每次循环一次就加锁
    mutex.acquire()
    num += 1
    # 解锁
    mutex.release()
    print("doing1=%s" % num)


    def doing2():
    global num
    for i in range(1000000):
    # 在循环内部加锁
    mutex.acquire()
    num += 1
    # 解锁
    mutex.release()
    print("doing2=%s" % num)


    def main():
    # 将mutex作为全局变量
    global mutex
    # 创建锁的对象
    mutex = threading.Lock()
    th_num1 = threading.Thread(target=doing1)
    th_num2 = threading.Thread(target=doing2)
    th_num1.start()
    th_num2.start()


    if __name__ == '__main__':
    main()

    上锁解锁过程

    1. 当一个线程调用锁的acquire()方法获得锁时,锁就进入locked状态
    2. 每次只有一个线程可以获得锁,如果此时另一个线程视图获得这个锁,该线程就会变为blocked状态,称为阻塞,知道拥有锁的线程调用锁的release()方法释放锁之后,锁进unlocked状态
    3. 线程调度程序从处于同步阻塞状态的线程中选择一个来获得锁,并使得该线程进入运行状态

    锁的参数和返回值

    1. 互斥锁是可以有参数和返回值的
    2. 返回值表示上锁是否成功
    3. blocking参数表示,是否是堵塞状态
    4. timeout参数表示,阻塞多少秒之后,变成非阻塞
    import threading

    multx = threading.Lock()
    for i in range(5):
    x = multx.acquire(blocking=True, timeout=3) # 只有在blocking为True的时候,使用timeout
    if x:
    print("加锁成功")
    else:
    print("加锁失败")

    小总结

    1. 如果是阻塞状态,那么会进行等待,不会有返回值
    2. 如果不是阻塞状态,那么返回值就是上锁成不成功

    线程-死锁

    1. 互斥锁,是一种锁
    2. 死锁,是一种现象,不是一个东西
    3. 一般情况下,有多个锁,才可能出现死锁的现象,说白了如果说两个锁,a和b,那么a锁后执行的代码等着b解锁,b锁后执行的代码等着a解锁,这就是一种死锁
    4. 避免死锁的办法,就是添加等待时间

    线程-锁的优缺点

    1. 锁的好处    确保了某段关键代码只能由一个线程从头到尾完整的执行
    2. 锁的坏处    阻止了多线程并发执行,包含锁的某段代码实际上只能以单线程模式执行,效率就打大的下降了,由于可以存在多个锁,不同的线程持有不同的锁,并试图获取对方持有的锁时,可能会造成死锁

    抢票案例

    import threading
    import time

    num = 1000000
    lock = None
    fre_1 = 0
    fre_2 = 0


    def work1():
    global num, fre_1
    while True:
    lock.acquire()
    if num == 0:
    break
    else:
    num -= 1
    fre_1 += 1
    lock.release()


    def work2():
    global num, fre_2
    while True:
    lock.acquire()
    if num == 0:
    break
    else:
    num -= 1
    fre_2 += 1
    lock.release()


    def main():
    global lock
    lock = threading.Lock()
    tr1 = threading.Thread(target=work1)
    tr1.start()
    tr2 = threading.Thread(target=work2)
    tr2.start()
    # 需要设置等待时间
    time.sleep(2)
    print("work1卖了:%d张" % fre_1)
    print("work2卖了:%d张" % fre_2)


    if __name__ == '__main__':
    main()

    多任务原理

    1. 什么叫多任务?    简单地说,就是操作系统可以同时运行多个任务,你一边在用浏览器上网,一边在听MP3,一边在用word赶作业这就是多任务,至少同时有3个任务正在运行,还有很多任务悄悄地在后台同时运行着,只是桌面上没有显示而已
    2. 单核cpu的工作原理
      1. 现在多核cpu已经非常普及了,但是即使过去的单核cpu,也可以执行多任务,由于cpu执行代码都是顺序执行的,那么单核cpu是怎么执行多任务呢?
      2. 也就是操作系统轮流让各个任务交替执行,任务1执行0.01秒,切换到任务2,任务2执行0.01秒,在切换到任务3,执行0.01秒...这样反复执行下去,表面上看,每个任务都是交替执行的,但是,由于cpu的执行速度实在是太快,我们感觉就像所有任务都在同时执行一样.
      3. 整整的并行执行多任务只能在多核cpu上实现,但是由于任务数量远远多于cpu的核心数量,所以操作系统也会自动把很多任务轮流调度到每个核心上执行
    3. 多核cpu工作原理    和单核类似,相当于多了一个干活的人
    4. 并发    指的是任务书多余cpu核数,通过操作系统的各种任务调度算法,实现用多个任务一起执行(实际上总有一些任务不在执行,因为切换任务的速度相当快,看上去一起执行而已)
    5. 并行    值的是任务书小于等于cpu核数,即任务真的是一起执行的
    6. 并行和并发都算是多任务单并行实际上才是真正的多任务,并发是假的

    线程的两种创建方式

    1. 直接使用threading模块的Thread类,指定要执行的方法,在调用start
    2. 使用继承的方式,继承Thread类,重新run方法,创建这个对象后,在调用start

    查看当前程序线程数量

    threading.enumerate()

    1. 获取所有线程,返回的是一个列表
    2. 如果需要个数,使用len(threading.enumerate())

    为子线程传参

    传参方式

    1. args    传递元祖
    2. kwargs    传递字典

    脚本实例

    import threading
    import time

    def work(num):
    for i in range(num):
    print("输入5次")
    time.sleep(1)


    def main():
    t1 = threading.Thread(target=work, args=(3,))
    t1.start()


    if __name__ == '__main__':
    main()


    import threading
    import time


    def work(a, b):
    for i in range(a):
    print("输入5次")
    time.sleep(1)


    def main():
    t1 = threading.Thread(target=work, kwargs={"a": 5, "b": 3})
    t1.start()


    if __name__ == '__main__':
    main()

    类继承方式为子线程传参

    import threading
    import time


    class Works(threading.Thread):
    def __init__(self, num):
    # threading.Thread.__init__(self) # 跟下面的super()是类似的
    super().__init__()
    self.num = num

    def run(self):
    for i in range(self.num):
    print("haha")


    def main():
    w = Works(2)
    w.start()


    if __name__ == '__main__':
    main()
  • 相关阅读:
    398. Random Pick Index
    382. Linked List Random Node
    645. Set Mismatch
    174. Dungeon Game
    264. Ugly Number II
    115. Distinct Subsequences
    372. Super Pow
    LeetCode 242 有效的字母异位词
    LeetCode 78 子集
    LeetCode 404 左叶子之和
  • 原文地址:https://www.cnblogs.com/wp950416/p/13325502.html
Copyright © 2011-2022 走看看