python 进程 线程

本文详细介绍了Python中的多进程和线程操作,包括如何创建进程、使用进程池,以及线程的状态、线程间通信和线程安全问题。通过示例展示了线程不安全的情况以及如何使用锁机制(如Lock、RLock和Semaphore)来确保线程安全。此外,还讨论了死锁和信号Semaphore、Event、Condition等高级概念。
摘要由CSDN通过智能技术生成

进程

进程实际上就是一个小的计算机资源单位,一个应用程序必须拥有且至少拥有一个进程,进程拥有自己独立的内存空间,多个进程之间不会互相干扰,在单核的CPU中,一个时刻只有一个进程运行,如果有多个进程需要同时运行的话,则需要在进程之间快速的切换,实际上还是只有一个进程,但是由于切换的速度较快,造成多进程同时运行的假象。

创建一个进程

要创建一个进程,首先需要从multiprocessing模块中导入Process,然后实例化一个进程就可以了,实例如下:

from multiprocessing import Process


def test_process(name):
	print("%s是一个进程,正在运行"%name)

if __name__ == "__main__":
	p = Process(target = test_process, args = ("p1",), name = "albert")
	p.start()
	p.join()
	print(p.name)
	print(p.pid)

在这里插入图片描述
这样就创建了一个进程,还可以创建一个依赖于时间的进程,比如:

from multiprocessing import Process
import time
import os

def test_process(name):
	n = 0
	while n<3:
		print(time.ctime()+"   "+"%s是一个进程,正在运行"%name+"   "+str(os.getpid()))
		time.sleep(1)
		n+=1

if __name__ == "__main__":
	p = Process(target = test_process, args = ("p1",), name = "albert")#这里参数传入的是一个元组
	p.start()
	p.join()

在这里插入图片描述
这个进程运行了三秒,使用os.getpid()可以获取当前进程的进程号,实际上定义一个函数,然后实例化一个进程,在参数target中传入需要运行的函数,args参数传入函数需要用的参数,注意这里应该是元组的形式。这样就创建了一个进程,然后使用p.start()开始进程,p.join()是父进程等待子进程结束再结束,否则如果父进程先结束了,而子进程还没有结束,则会造成僵尸进程,也成为孤儿进程。join函数还可以传入等待时间,使用terminate()方法会直接终结进程,不管子进程是否结束。
然后将这样的方法修改成为类,利用类的方式来创建一个进程

from multiprocessing import Process
import time
import os


class MyProcess(Process):

	def __init__(self, my_time):
		Process.__init__(self)
		self.my_time = my_time
		
	def run(self):
		n = 0
		while n<3:	
			print(time.ctime()+"   "+"%s是一个进程,正在运行"%self.name+"   "+str(os.getpid()))
			time.sleep(self.my_time)
			n+=1
	
if __name__ == "__main__":
	p = MyProcess(2)#这里参数传入的是一个元组
	p.start()
	p.join()

在这里插入图片描述
这样就可以利用类来创建进程了,但是这只是单进程,单核的时候同一个时刻只有一个进程,但是现在的CPU大多都是多核的,所以创建多进程可以有效地增加程序的运行效率。

进程池

在python中可以使用进程池来创建多进程,因为创建进程会消耗大量的资源,创建的数量要求较多的话就会不合适,如果有进程池的话,当进程池中有空闲进程就拿来用,没有的话就等待,这样就会节省时间和资源。例如:

from multiprocessing import Process, Pool
import time
import os


def run_process(my_time, name):
	n = 0
	while n<4:	
		print(time.ctime()+"   "+"%s是一个进程,正在运行"%name+"   "+str(os.getpid()))
		time.sleep(my_time)
		n+=1
	
if __name__ == "__main__":
	start_time = time.time()
	p = Pool(4)
	for i in range(16):
		p.apply_async(run_process, args = (0.5, "muilt_process%s"%i, ))
	print("waiting...")
	p.close()
	p.join()
	end_time = time.time()
	p_time = end_time - start_time
	print("%0.2f"%p_time)

在这里插入图片描述
在这里插入图片描述
可以看出一共执行了16个进程,每个进程消耗时间为0.5*4秒,一共应该用时为32秒,但是使用多进程,四个进程同时运行,加上进程间切换的时间,实际用时只有8.54秒。采用close方法是执行多进程的时候不在接收新的进程,运行完已有进程后关闭,与terminate不一样。

线程

线程是进程中单一连续的控制流程,是进程的一部分,一个进程至少有一个线程,多个线程可以属于同一个进程,且这些线程共享进程的内存空间,所以线程可以访问全局变量。
在python中,有两个包可以创建线程,thread与threading,后者比前者封装更加的完整,功能也更加的完善,所以直接掌握后者就可以了。首先利用threading来创建一个线程:

import threading
import time

def t_test(name):
	n = 0
	while n<3:
		print("%s is runing, it's a threading"%name)
		time.sleep(1)
		n+=1
		
if __name__ == "__main__":
	t = threading.Thread(target=t_test, args=("thread1",))
	t.start()
	t.join()
	

在这里插入图片描述
加入join()方法是为了让线程可以正常结束,否则在多线程的时候可能会造成类似于僵尸进程的僵尸线程,现在将这个线程改写为类的形式

import threading
import time

class ThreadTest(threading.Thread):

	def __init__(self, t_name):
		super(ThreadTest, self).__init__(name=t_name)
		
	def run(self):
		n=0
		while n<3:
			print("%s is runing"%self.name)
			time.sleep(1)
			n+=1

			
if __name__ == "__main__":
	t = ThreadTest("thread1")
	t.start()
	t.join()
	

在这里插入图片描述
多线程的实现,也就是同时创建多个线程,可以同时执行多个任务。

import threading
import time

def t_test(name):
	n = 0
	while n<3:
		print("%s is runing, it's a threading"%name)
		time.sleep(1)
		n+=1
thread_list=[]
for i in range(10):
	t = threading.Thread(target=t_test, args=("thread"+str(i),))		
	thread_list.append(t)
	
if __name__ == "__main__":
	s_time = time.time()
	for item in thread_list:
		item.start()
	for item in thread_list:
		item.join()
	e_time = time.time()
	print("totlely used %0.2f s"%(e_time-s_time))

在这里插入图片描述
在这里插入图片描述
可以看出效果非常明显,原本每个线程休眠三秒,十个线程一共需要休眠三十秒,但是采用多线程使得这些任务同时休眠,一共耗费三秒就够了。

线程的状态

既然可以运行的这么快,那么原理是什么呢?这就需要了解线程的状态,线程运行一共有三种状态,阻塞,就绪,运行中,再加上创建和终止,也就是线程一共有五种状态。对于创建和终止这里不多做赘述,来了解一下运行的三种状态

  1. 阻塞:访问资源但是资源并未准备好
  2. 就绪:资源已经准备好了,等待调度
  3. 运行中:调度轮到该线程,然后运行线程代码

这三种状态可以相互转化,也可以设置优先级,每个线程都有着自己运行的时间,当自己的运行时间耗尽时,自身代码没有执行完成,就需要转入就绪状态等待下一次的调度。

线程间通信

线程有一个特点就是共享全局变量,多个线程之间共享同一个内存空间,这样的话当一个线程运行时间耗尽,没有执行完代码,需要转入就绪状态,加入在这个过程中该线程修改了某一个变量,而其他的线程有需要使用这个变量,就会造成线程不安全。
首先为了解决线程安全可以采用传参的方式使用全局变量,这样对于不可变对象而言,实际线程运行的时候使用的实际上是一个自己的变量,不会影响其他线程,但是对于可变对象而言,还是会造成全局变量的修改,所以就需要讨论一下线程的同步问题了。
对于线程的安全,频繁的访问修改同一个全局变量,时间线的切换可能会产生错误,这是因为计算机在运行时,首先将内存中的资源读入寄存器中,然后再计算,返回结果,线程的工作分为几步运行,当有别的线程打断时,就会不安全,所以可以加同步机制来消除这种隐患,也就是加锁。

线程安全

首先示例一个线程不安全的情况

# -*- coding: utf-8 -*-
"""
Created on Thu May  9 13:49:00 2019

@author: Albert
"""
import threading
import time

num = 0

def add():
    global num
    for i in range(100000):
        num += 1
    print("the num value is %s, the No. is %s"%(num, i)) 

for j in range(3):
    t = threading.Thread(target=add)
    t.start()
    
time.sleep(3)
print("last value is %s"%num)

结果为

runfile('C:/Users/nx005551/Code/python_csdn/unsafe_thread.py', wdir='C:/Users/nx005551/Code/python_csdn')
the num value is 100000, the No. is 99999the num value is 150894, the No. is 99999
the num value is 250894, the No. is 99999

last value is 250894

可以看出三个线程同时计算就会变得不准确,由于线程切换很快,所以产生了错误。
为了使结果准确,可以对线程加锁,在python中,threading模块提供了一下集中锁机制

  1. Lock互斥锁
  2. RLock可重入锁
  3. Semaphore信号
  4. Event事件
  5. Condition条件
互斥锁Lock

互斥锁是一种独占锁,一个时刻只能有一个线程可以访问共享数据,使用需要先实例化一个锁对象,然后将锁当参数传入函数中,对不可分开的操作进行加锁即可

# -*- coding: utf-8 -*-
"""
Created on Thu May  9 13:49:00 2019

@author: Albert
"""
import threading
import time

num = 0

mutex = threading.Lock()

def add(p_mutex):
    global num
    p_mutex.acquire()
    for i in range(100000):
        num += 1
    print("the num value is %s"%num)
    p_mutex.release() 

if __name__ == "__main__":
    for j in range(3):
        t = threading.Thread(target=add, args=(mutex, ))
        t.start()
        
    time.sleep(3)
    print("last value is %s"%num)

运行结果显示正常

runfile('C:/Users/nx005551/Code/python_csdn/safe_thread.py', wdir='C:/Users/nx005551/Code/python_csdn')
the num value is 100000
the num value is 200000
the num value is 300000
last value is 300000
可重入锁RLock与死锁

死锁,就是多个线程共享多个资源,分别占有对方的资源且等待对方的资源,这样就会造成死锁。可重入锁RLock与Lock使用方法相同,不同的是可重入锁支持重入,相当于有多把钥匙,这样就可以多个线程进入锁,但是钥匙数量有限,所以等数量到达限度之后,后来的线程就需要等待。所以可以通过加锁来控制线程的执行顺序。

信号Semaphore

BoundedSemaphore,它支持一定数量的线程同时修改数据,不同于互斥锁,相当于一个缓冲区,允许待一定数量的线程。


import threading
import time
    
def thread_print(no, mutex_re):
    mutex_re.acquire()
    print("the thread %s is runing"%no)
    time.sleep(1)
    mutex_re.release()

semaphore = threading.BoundedSemaphore(3)
for i in range(15):
    thread_test = threading.Thread(target=thread_print, args=(i, semaphore))
    thread_test.start()
    
runfile('C:/Users/nx005551/Code/python_csdn/thread3.py', wdir='C:/Users/nx005551/Code/python_csdn')
the thread 0 is runing
the thread 1 is runing
the thread 2 is runing
the thread 4 is runing
the thread 3 is runing
the thread 5 is runing
the thread 6 is runing
the thread 8 is runing
the thread 7 is runing
the thread 9 is runing
the thread 10 is runing
the thread 11 is runing
the thread 12 is runing
the thread 13 is runing
the thread 14 is runing
时间Event

事件线程锁的运行机制:全局定义了一个Boolean,如果Boolean的值为False,那么当程序执行wait()方法时就会阻塞,如果Boolean值为True,线程不再阻塞。这种锁,类似交通红绿灯(默认是红灯),它属于在红灯的时候一次性阻挡所有线程,在绿灯的时候,一次性放行所有排队中的线程。这种锁一共有四种方法,分别是set()将布尔值设置为True,clear()将布尔值设置为False,wait()为等待,is_set()判断现在是否是True,如果是,就执行线程代码,否则不执行。

条件Condition

Condition称作条件锁,依然是通过acquire()/release()加锁解锁。
wait([timeout])方法将使线程进入Condition的等待池等待通知,并释放锁。使用前线程必须已获得锁定,否则将抛出异常。
notify()方法将从等待池挑选一个线程并通知,收到通知的线程将自动调用acquire()尝试获得锁定(进入锁定池),其他线程仍然在等待池中。调用这个方法不会释放锁定。使用前线程必须已获得锁定,否则将抛出异常。
notifyAll()方法将通知等待池中所有的线程,这些线程都将进入锁定池尝试获得锁定。调用这个方法不会释放锁定。使用前线程必须已获得锁定,否则将抛出异常。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值