进程:
进程就是一个程序在一个数据集上的一次动态执行过程。进程一般由程序、数据集、进程控制块三部分组成。我们编写的程序用来描述进程要完成哪些功能以及如何完成;数据集则是程序在执行过程中用它来控制和管理进程,它是系统感知进程存在的唯一标识。
为了实现多道技术,使CPU使用率更高,系统会经常进行进行间的切换,切换会发生在出现IO操作的时候,以及系统固定时间的切换。
线程:
线程的出现是为了降低上下文切换的消耗,提高系统的并发性,并突破一个进程只能干一样事的缺陷,使到进程内并发成为可能。线程也叫轻量级进程,它是一个基本的CPU执行单元,也是程序执行过程中的最小单元,由线程ID、程序计数器、寄存器集合和堆栈共同组成。线程的引入减小了程序并发执行时的开销,提高了操作系统的并发性能。线程没有自己的系统资源。
进程与线程的关系:
1.一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。
2.资源分配给进程,同一进程的所有线程共享进程的所有资源。
3.CPU分给线程,即真正在CPU上进行的是线程。
Pyhon的多线程:由于GIL导致同一时刻同一进程只能有一个线程。
并行和并发:
并行处理(Parallel Processing)是计算机系统中能同时执行两个或更多个处理的一种计算方法。并行处理可同时工作于同一程序的不同方面。并行处理的主要目的是节省大型和复杂问题的解决时间。并发处理(concurrency Processing):指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机(CPU)上运行,但任一个时刻点上只有一个程序在处理机(CPU)上运行
并发的关键是你有处理多个任务的能力,不一定要同时。并行的关键是你有同时处理多个任务的能力。所以说,并行是并发的子集
threading模块
线程对象的创建:
thread直接创建:
1 1 import time 2 2 import threading 3 3 def tingge(): 4 4 print('听歌') 5 5 time.sleep(3) 6 6 print('听歌结束') 7 7 8 8 def xieboke(): 9 9 print('写博客') 10 10 time.sleep(5) 11 11 print('写博客结束') 12 12 13 13 t1 = threading.Thread(target = tingge) #生成线程实例,target=函数名,args = 函数需要传参的参数,默认可以不传 14 14 t2 = threading.Thread(target = xieboke) 15 15 16 16 t1.start() #启动进程 17 17 t2.start() 18 18 print("ending!") 19 20 21 #继承Thread式自定义创建: 22 import threading 23 import time 24 class MyThread(threading.Thread): 25 26 def __init__(self,num): 27 threading.Threa.__init__(self) 28 self.num = num 29 def run(self): 30 print("running on number:%s" % self.num ) 31 time.sleep(3) 32 33 t1 = MyThread(56) 34 t2 = MyThread(78) 35 36 t1.start() 37 t2.start() 38 print('ending')
以上第一段代码就实现了一个并发的效果。tingge与xieboke函数(子线程)跟主逻辑“print('ending!')”(主线程)同时启动,先几乎同时执行【print('听歌')、print('写博客')、print("ending!")】随后IO操作“sleep”,等3S听歌结束,再等2S写博客结束。如果使用串行的方法,程序全部执行则一共需要差不多8S的时间,降低了CPU的使用效率。
Threading的方法:
join:在子线程完成运行之前,这个子线程的父线程将一直被阻塞。
setDaemon:
''' 将线程声明为守护线程,必须在start() 方法调用之前设置,如果不设置为守护线程程序会被无限挂起。 当我们在程序运行中,执行一个主线程,如果主线程又创建一个子线程,主线程和子线程 就分兵两路,分别运行,那么当主线程完成 想退出时,会检验子线程是否完成。如果子线程未完成,则主线程会等待子线程完成后再退出。但是有时候我们需要的是只要主线程 完成了,不管子线程是否完成,都要和主线程一起退出,这时就可以 用setDaemon方法啦'''
这么看来,是不是当要执行多个线程,是不是都可以用Threading来解决呢?
其实并不是,对于计算密集型任务,Python的多线程并没有用。由于GIL的存在在,一个线程拥有了解释器的访问权之后,其他的所有线程都必须等待它释放解释器的访问权,即使这些线程的下一条指令并不会互相影响。
因此Python并不能达到一个并发的效果,同一时间只能运行一个线程,所以对于计算密集型任务,多个线程同时启动,CPU就需要频繁的来回切换,这样就加入了大量的切换时间。而串行的方式则只需要切换少数的几次,
因此对于计算密集型任务,Python的多线程并没有用,而对于上面的IO密集型任务,Python的多线程是有意义的。
那么Python有没有使用多核的方法呢? 答案是可以的,但只能是开多个进程来实现,那样弊端也显而易见,会增大资源开销而且进程间相互的切换也会非常复杂。
解决的着重点:协程+多进程/ IO多路复用
同步锁:LOCK
多线程和多进程最大的不同在于,多进程中,同一个变量,各自有一份拷贝存在于每个进程中,互不影响,而多线程中,所有变量都由所有线程共享,所以,任何一个变量都可以被任何一个线程修改,因此,线程之间共享数据最大的危险在于多个线程同时改一个变量,把内容给改乱了。
import time import threading def subNum(): global num lock.acquire() #加锁 temp = num time.sleep(0.001) #定义一个num-1的函数 每次运算前停留0.001S num = temp -1 num = 100 #定义一个初始数值num =100 thread_list = [ ] for i in range(100): t = threading.Thread(target = subNum) #开启100个子线程来运行减法函数 t.start() thread_list.append(t) for t in thread_list: t.join() #阻塞主线程运行,所有减法函数执行完得出最后结果 ptiny('Result:',num)
import time import threading def subNum(): global num print("验证执行") lock.acquire() #加锁 temp = num time.sleep(0.1) num = temp -1 lock.release() #解锁 num = 100 thread_list = [ ] lock = threading.Lock() #定义一个同步锁 for i in range(100): t = threading.Thread(target = subNum) t.start() thread_list.append(t) for t in thread_list: t.join() ptiny('Result:',num)
1 import threading 2 import time 3 4 mutexA = threading.Lock() 5 mutexB = threading.Lock() 6 7 class MyThread(threading.Thread): 8 def __init__(self): 9 threading.Thread.__init__(self) 10 11 def run(self): 12 self.fun1() 13 self.fun2() 14 15 def fun1(self): 16 mutexA.acquire() # 如果锁被占用,则阻塞在这里,等待锁的释放 17 18 print("I am %s, get res: %s---%s" % (self.name, "ReaA", time.time())) 19 20 mutexB.acquire() 21 print("I am %s, get res: %s---%s" % (self.name, "RreB", time.time())) 22 mutexB.release() 23 24 mutexA.release() 25 26 def fun2(self): 27 mutexB.acquire() 28 print("I am %s, get res: %s---%s" % (self.name, "ResB", time.time())) 29 time.sleep(0.2) 30 31 mutexA.acquire() 32 print("I am %s, get res: %s---%s" % (self.name, "ResA", time.time())) 33 mutexA.release() 34 35 mutexB.release() 36 37 if __name__ == "__main__": 38 print("start------------%s" % time.time()) 39 40 for i in range(0, 10): 41 my_thread = MyThread() 42 my_thread.start()
当代码中有2个锁,锁中套着锁,多个线程同时执行时,可以看到,正常情况下。每个线程会依次A、B、B、A的顺序进行打印,一共10个线程的循环。实际运行的情况是,当线程1走完fun1(),走到线程2时,打印完B,即获得B锁,sleep0.2S,此时,线程2已经竞争到了A锁,而B锁已经被线程1给拿到,因此在此处阻塞,而线程1sleep完成后,跟着执行代码时,A锁已经被线程2占用,所以它也不能执行,2个线程相互胶着互不放手,这样就触发了死锁。如果要解决这个问题就需要用到另一种锁即递归锁。
在Python中为了支持在同一线程中多次请求同一资源,python提供了可重入锁RLock。这个RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次require。直到一个线程所有的acquire都被release,其他的线程才能获得资源。上面的例子如果使用RLock代替Lock,则不会发生死锁:
1 import threading 2 import time 3 4 mutex = threading.RLock() 5 6 class MyThread(threading.Thread): 7 8 def __init__(self): 9 threading.Thread.__init__(self) 10 11 def run(self): 12 self.fun1() 13 self.fun2() 14 15 def fun1(self): 16 17 mutex.acquire() # 如果锁被占用,则阻塞在这里,等待锁的释放 18 19 print ("I am %s , get res: %s---%s" %(self.name, "ResA",time.time())) 20 21 mutex.acquire() 22 print ("I am %s , get res: %s---%s" %(self.name, "ResB",time.time())) 23 mutex.release() 24 25 mutex.release() 26 27 28 def fun2(self): 29 30 mutex.acquire() 31 print ("I am %s , get res: %s---%s" %(self.name, "ResB",time.time())) 32 time.sleep(0.2) 33 34 mutex.acquire() 35 print ("I am %s , get res: %s---%s" %(self.name, "ResA",time.time())) 36 mutex.release() 37 38 mutex.release() 39 40 if __name__ == "__main__": 41 42 print("start---------------------------%s"%time.time()) 43 44 for i in range(0, 10): 45 my_thread = MyThread() 46 my_thread.start()