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()
  • 相关阅读:
    cve-2015-1635 poc
    Python实现ORM
    Android完全退出应用的方法
    Java反射理解
    Android动画
    Android进程间通信IPC
    Java的四种引用方式
    Android底部菜单的实现
    Android中AsyncTask使用
    Android自定义控件
  • 原文地址:https://www.cnblogs.com/wp950416/p/13325502.html
Copyright © 2011-2022 走看看