python-进程补充和线程理论

image

僵尸进程与孤儿进程

僵尸进程
进程代码运行结束之后并没有直接结束而是需要等待收回子进程资源才能结束。
孤儿进程
主进程已经死亡(非正常)但是子进程还在运行

守护进程

守护某个进程,一旦这个进程结束那么也随之结束
from multiprocessing import Process
import time


def judy(name):
	print('%s is working ' % name)
	time.sleep(1)
	print('%s finished her work')


if __name__ == '__main__':
	p = Process(target=judy, args=('judy',))
	p.daemon = True  ### 这个命令是将开启的进程变成主进程的守护进程
	p.start()
	print('who is first')

互斥锁

在并发的情况下,如果操作同一份数据的话,极其容易造成数据错乱,那么只有将并发变成串行,虽然降低了效率但是提升了数据安全。

举个例子:

import json
from multiprocessing import Process


def check(name):
	with open(r'test.txt', 'r', encoding='utf8') as f:
		data_dict = json.load(f)
		cat_number = data_dict['cat']
		print('%s查看了余票,还有:%s张' % (name, cat_number))


def buy(name):
	with open(r'test.txt', 'r', encoding='utf8') as f:
		data_dict = json.load(f)
		cat_number = data_dict['cat']
		if cat_number > 0:
			cat_number -= 1
			data_dict['cat'] = cat_number
			with open(r'test.txt', 'w', encoding='utf8') as f:
				json.dump(data_dict, f)
				print('%s 抢到了票,余票剩余 %s' % (name, cat_number))
		else:
			print('没有票了没有票了')


def run(name):
	check(name)
	buy(name)


if __name__ == '__main__':
	for i in range(2):
		p = Process(target=run,args=(i,))
		p.start()

结果:

image
所以这样是不行的,必须得加上互斥锁!!

正确的版本应该是这样的!!!!!

import json
from multiprocessing import Process, Lock


def check(name):
	with open(r'test.txt', 'r', encoding='utf8') as f:
		data_dict = json.load(f)
		cat_number = data_dict['cat']
		print('%s查看了余票,还有:%s张' % (name, cat_number))


def buy(name):
	with open(r'test.txt', 'r', encoding='utf8') as f:
		data_dict = json.load(f)
		cat_number = data_dict['cat']
		if cat_number > 0:
			cat_number -= 1
			data_dict['cat'] = cat_number
			with open(r'test.txt', 'w', encoding='utf8') as f:
				json.dump(data_dict, f)
				print('%s 抢到了票,余票剩余 %s' % (name, cat_number))
		else:
			print('没有票了没有票了')


def run(name, mutex):
	check(name)
	mutex.acquire()
	buy(name)
	mutex.release()


if __name__ == '__main__':
	mutex = Lock() #在主进程里面产生锁
	for i in range(2):
		p = Process(target=run, args=(i, mutex)) ###要把锁穿进去!!!!
		p.start()

结果

image

🌹 使用锁的注意事项
1、🔒 必须在主进程中产生,交由子进程使用
2、不要轻易地使用锁,容易造成死锁现象

消息队列-->用于打破进程间默认无法通信的情况

队列(queue)先进先出的原则

from multiprocessing import Queue

q=Queue(3) # 括号里的数字表示消息队列里面放几个,如果超出范围的话会在原地等待,当然等待时间是可以设置的
q.put('judy')
q.put('loves')
print(q.full()) # 判断队列是否满了
q.put('cats')
print(q.empty()) #判断队列是否为空
for i in range(3): 
	print(q.get())
# print(q.get_nowait()) -->如果队列里面没有数据的话会立马报错

⚠️ 注意事项: full和get_nowait是不可以用于多进程情况下的

IPC机制

from multiprocessing import Process, Queue


def maker():
	q.put('judy loves cats')


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


if __name__ == '__main__':
	q = Queue()
	p = Process(target=maker)
	p1 = Process(target=customer)
	p.start()
	p1.start()

🌹 结果
image

生产者消费者模型

from multiprocessing import Process, Queue


def maker(name, food):
	for i in range(2):
		q.put(food)
		print('%s puts %s' % (name, food))


def customer(name):
	while True:
		print('%s 吃了 %s' % (name, q.get()))


if __name__ == '__main__':
	q = Queue()
	p = Process(target=maker, args=('judy', '包子'))
	p1 = Process(target=maker, args=('amber', '油条'))
	C = Process(target=customer, args=('tom',))
	p.start()
	p1.start()
	C.start()

🌹 结果
image
⚠️:从结果可以看出来,这样的话进程无法结束,C这个进程会一直等着队列中有数据进来。
🌹 解决方法 --> 用JoinableQueue模块

from multiprocessing import Process, JoinableQueue
import random
import time


def maker(name, food):
	for i in range(2):
		time.sleep(random.random())
		q.put(food)
		print('%s puts %s' % (name, food))


def customer(name):
	while True:
		print('%s 吃了 %s' % (name, q.get()))
		q.task_done()


if __name__ == '__main__':
	q = JoinableQueue()
	p = Process(target=maker, args=('judy', '包子'))
	p1 = Process(target=maker, args=('amber', '油条'))
	C = Process(target=customer, args=('tom',))
	p.start()
	p1.start()
	C.daemon=True
	C.start()
	p1.join()
	p.join() --->得用join方法让主进程等两个子进程结束了

线程理论

进程是一个资源单位,真正被CPU执行的其实是进程里面的线程,值得注意的是进程之间的数据是默认隔离的,但是同一个进程内的多个线程的数据是共享的。

开设线程的两种方式

⚠️ 开设进程需要做哪些操作
1、重新申请一块内存空间
2、将所需的资源全部导入

🌹 方式1

from threading import Thread

def myThread():
	print('judy judy')

obj=Thread(target=myThread)
obj.start()

🌹方式2

from threading import Thread


class myThread(Thread):
	def __init__(self, name):
		super().__init__()
		self.name = name

	def run(self) -> None:
		print('I am %s' % self.name)


obj = myThread()
obj.start()

线程对象的其他方法

1、join方法 主线程等待子线程结束
2、active_count统计当前正在活跃的线程数
3、current_thread 获取当前线程的名字

守护线程

主线程的结束意味着整进程的结束,所以祝线程需要等待里面所有非守护进程结束才能结束。

from threading import Thread
from multiprocessing import Process
import time


def foo():
	print(123)
	time.sleep(3)
	print('end123')


def bar():
	print(456)
	time.sleep(1)
	print('end456')


if __name__ == '__main__':
	t1 = Thread(target=foo)
	t2 = Thread(target=bar)
	t1.daemon = True
	t1.start()
	t2.start()
	print('main')

🌼 结果
image
⚠️ 在结果中并没有end123,因为线程t1是祝线程的守护进程,所以当其他非守护线程结束后,主线程立即结束,此时,t1可能还在io,也立即结束,没来得及打印end123.

线程数据共享

from threading import Thread
money = 100

def test():
	global money
	money = 999

t = Thread(target=test)
t.start()
t.join()
print(money)

线程互斥锁

from threading import Thread
from multiprocessing import Lock
import time

num = 5


def test():
	global num
	tmp = num
	time.sleep(5)
	tmp -= 1
	num = tmp


t_list = []

for i in range(5):
	t = Thread(target=test)
	t.start()
	t_list.append(t)

for i in t_list:
	i.join()
print(num)

image

⚠️:那结果为什么是4呢?是因为歌线程io时间过长,各个线程都拿到了原始数据,都是改的同一份数据。此时为了保证数据不错乱,我们必须用上互斥锁

from threading import Thread
from multiprocessing import Lock
import time

num = 5


def test():
	mutex.acquire()
	global num
	tmp = num
	time.sleep(0.1)
	tmp -= 1
	num = tmp
	mutex.release()
mutex=Lock()
t_list = []

for i in range(5):
	t = Thread(target=test)
	t.start()
	t_list.append(t)

for i in t_list:
	i.join()
print(num)

TCP实现服务端并发

import socket
from threading import Thread
from multiprocessing import Process



new_socket=socket.socket()
new_socket.bind(('127.0.0.1',8080))
new_socket.listen()

def talk():
	while True:
		try:
			data=conn.recv(1024)
			conn.send(data.upper())
		except Exception:
			print('')

while True:
	conn,addr = new_socket.accept()
	t = Thread(target=talk,args=(conn,))
	t.start()

⚠️:其实就是相当于把通信循环和链接循环交给两个人去做,这样就实现了并发效果

补充

悲观锁
总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。Java中synchronized和ReentrantLock等独占锁就是悲观锁思想的实现。

乐观锁
总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制和CAS算法实现。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于write_condition机制,其实都是提供的乐观锁。在Java中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。

两种锁的使用场景
从上面对两种锁的介绍,我们知道两种锁各有优缺点,不可认为一种好于另一种,像乐观锁适用于写比较少的情况下(多读场景),即冲突真的很少发生的时候,这样可以省去了锁的开销,加大了系统的整个吞吐量。但如果是多写的情况,一般会经常产生冲突,这就会导致上层应用会不断的进行retry,这样反倒是降低了性能,所以一般多写的场景下用悲观锁就比较合适。
posted @ 2022-01-14 17:21  JudyJU  阅读(38)  评论(0)    收藏  举报