进程与线程

 
 
 
 

进程与线程

进程

进程的创建

  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()
posted @ 2020-07-16 22:13  SunFree  阅读(207)  评论(0编辑  收藏  举报