一、进程与线程
1.什么是进程(process)?
程序并不能单独运行,只有将程序装载到内存中,系统为它分配资源才能运行,而这种执行的程序就称之为进程。程序和进程的区别就在于:程序是指令的集合,它是进程运行的静态描述文本;进程是程序的一次执行活动,属于动态概念。
在多道编程中,我们允许多个程序同时加载到内存中,在操作系统的调度下,可以实现并发地执行。这是这样的设计,大大提高了CPU的利用率。进程的出现让每个用户感觉到自己独享CPU,因此,进程就是为了在CPU上实现多道编程而提出的。
注:进程就是一堆资源的集合
2.什么是线程(thread)?
线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务
注:线程是操作系统最小的调度单位, 是一串指令的集合
TIp1:
进程 要操作cpu , 必须要先创建一个线程 ,
all the threads in a process have the same view of the memory
所有在同一个进程里的线程是共享同一块内存空间的
TIp2:
1.进程和线程没有可比性,进程是一堆资源的集合,而线程是一堆指令的集合
2.启动一个线程比启动一个进程快
3.进程与线程的区别?
Threads share the address space of the process that created it; processes have their own address space.
线程共享内存空间,进程的内存是独立的
Threads have direct access to the data segment of its process; processes have their own copy of the data segment of the parent process.
线程可以直接访问进程的数据段;进程有自己的父进程的数据段的副本。
Threads can directly communicate with other threads of its process; processes must use interprocess communication to communicate with sibling processes.
同一个进程的线程之间可以直接交流,两个进程想通信,必须通过一个中间代理来实现
New threads are easily created; new processes require duplication of the parent process.
创建新线程很简单, 创建新进程需要对其父进程进行一次克隆
Threads can exercise considerable control over threads of the same process; processes can only exercise control over child processes.
一个线程可以控制和操作同一进程里的其他线程,但是进程只能操作子进程
Changes to the main thread (cancellation, priority change, etc.) may affect the behavior of the other threads of the process; changes to the parent process does not affect child processes.
主线程(取消、优先级更改等)的更改可能会影响进程的其他线程的行为;父进程的更改不会影响子进程。
二、线程
1.直接调用
1 import threading 2 import time 3 4 def run(x): 5 print("task:", x) 6 time.sleep(2) 7 8 t1 = threading.Thread(target=run, args=("t1",)) 9 t2 = threading.Thread(target=run, args=("t2",)) 10 t1.start() 11 t2.start()
2.继承式调用
1 import threading 2 import time 3 4 class MyThread(threading.Thread): 5 6 def __init__(self, n): 7 super(MyThread, self).__init__() 8 self.n = n 9 10 def run(self): 11 print("running task:", self.n) 12 time.sleep(2) 13 14 t1 = MyThread("t1") 15 t2 = MyThread("t2") 16 t1.start() 17 t2.start()
2.1join等待线程执行完毕
1 import threading 2 import time 3 4 def run(x): 5 print("running task:", x, threading.active_count()) 6 time.sleep(2) 7 print("task%s done" % x, threading.current_thread()) 8 9 start_time = time.time() 10 t_objs = [] 11 for i in range(50): 12 t = threading.Thread(target=run, args=("t%s" % i,)) 13 t.start() 14 t_objs.append(t) 15 16 for t in t_objs: 17 t.join() 18 19 print("all threads done", threading.current_thread(), threading.active_count()) 20 print("cost:", time.time() - start_time)
3.守护线程
1 import threading 2 import time 3 4 def run(x): 5 print("running task:", x, threading.active_count()) 6 time.sleep(2) 7 print("task%s done" % x, threading.current_thread()) 8 9 start_time = time.time() 10 t_objs = [] 11 for i in range(50): 12 t = threading.Thread(target=run, args=("t%s" % i,)) 13 t.setDaemon(True) # 把当前线程设置为守护线程 14 t.start() 15 t_objs.append(t) 16 17 print("all threads done", threading.current_thread(), threading.active_count()) 18 print("cost:", time.time() - start_time)
4.为什么有GIL锁
GIL:无论你启多少个线程,你有多少个cpu, Python在执行的时候会淡定的在同一时刻只允许一个线程运行
首先需要明确的一点是GIL并不是Python的特性,它是在实现Python解析器(CPython)时所引入的一个概念。就好比C++是一套语言(语法)标准,但是可以用不同的编译器来编译成可执行代码。有名的编译器例如GCC,INTEL C++,Visual C++等。Python也一样,同样一段代码可以通过CPython,PyPy,Psyco等不同的Python执行环境来执行。像其中的JPython就没有GIL。然而因为CPython是大部分环境下默认的Python执行环境。所以在很多人的概念里CPython就是Python,也就想当然的把GIL归结为Python语言的缺陷。所以这里要先明确一点:GIL并不是Python的特性,Python完全可以不依赖于GIL。
总结:
Python GIL其实是功能和性能之间权衡后的产物,它尤其存在的合理性,也有较难改变的客观因素。
– 因为GIL的存在,只有IO Bound场景下得多线程会得到较好的性能
– 如果对并行计算性能较高的程序可以考虑把核心部分也成C模块,或者索性用其他语言实现
– GIL在较长一段时间内将会继续存在,但是会不断对其进行改进
5.线程锁(互斥锁)
1 import threading 2 import time 3 4 def change(): 5 lock.acquire() # 加锁 6 global num 7 num += 1 # 加锁和释放锁之间要保证快速计算,避免占着茅坑不拉屎现象 8 lock.release() # 释放锁 9 time.sleep(1) 10 11 num = 0 12 lock = threading.Lock() # 获取一把锁 13 14 for i in range(50): 15 t = threading.Thread(target=change) 16 t.start() 17 18 print(num)
为什么Python有GIL了,还需要互斥锁呢?这两个是不一样的,下面的图可以解释:
即:修改数据的代码执行到中途由于执行时间到了,被强制要求释放GIL,这时就会造成修改数据乱套。
既然用户程序已经自己有锁了,那为什么C python还需要GIL呢?加入GIL主要的原因是为了降低程序的开发的复杂度,比如现在的你写python不需要关心内存回收的问题,因为Python解释器帮你自动定期进行内存回收,你可以理解为python解释器里有一个独立的线程,每过一段时间它起wake up做一次全局轮询看看哪些内存数据是可以被清空的,此时你自己的程序 里的线程和 py解释器自己的线程是并发运行的,假设你的线程删除了一个变量,py解释器的垃圾回收线程在清空这个变量的过程中的clearing时刻,可能一个其它线程正好又重新给这个还没来及得清空的内存空间赋值了,结果就有可能新赋值的数据被删除了,为了解决类似的问题,python解释器简单粗暴的加了锁,即当一个线程运行时,其它人都不能动,这样就解决了上述的问题, 这可以说是Python早期版本的遗留问题。
6.递归锁
1 import threading 2 3 def run1(): 4 print("grab the first part data") 5 lock.acquire() 6 global num 7 num += 1 8 lock.release() 9 return num 10 11 def run2(): 12 print("grab the second part data") 13 lock.acquire() 14 global num2 15 num2 += 1 16 lock.release() 17 return num2 18 19 def run3(): 20 lock.acquire() 21 res = run1() 22 print('--------between run1 and run2-----') 23 res2 = run2() 24 lock.release() 25 print(res, res2) 26 27 if __name__ == '__main__': 28 29 num, num2 = 0, 0 30 lock = threading.RLock() 31 for i in range(10): 32 t = threading.Thread(target=run3) 33 t.start() 34 35 while threading.active_count() != 1: 36 print(threading.active_count()) 37 else: 38 print('----all threads done---') 39 print(num, num2)
7.信号量(semaphore)
互斥锁 同时只允许一个线程更改数据,而Semaphore是同时允许一定数量的线程更改数据 ,比如厕所有3个坑,那最多只允许3个人上厕所,后面的人只能等里面有人出来了才能再进去。
1 import threading 2 import time 3 4 def run(n): 5 semaphore.acquire() 6 time.sleep(1) 7 print("run the thread: %s " % n) 8 semaphore.release() 9 10 if __name__ == '__main__': 11 12 semaphore = threading.BoundedSemaphore(5) # 最多允许5个线程同时运行 13 for i in range(20): 14 t = threading.Thread(target=run, args=(i,)) 15 t.start() 16 17 while threading.active_count() != 1: 18 pass # print threading.active_count() 19 else: 20 print('----all threads done---')
8.Timer
This class represents an action that should be run only after a certain amount of time has passed
这个类代表一个动作,只有在一定数量的时间通过后才能运行
Timers are started, as with threads, by calling their start() method. The timer can be stopped (before its action has begun) by calling thecancel() method. The interval the timer will wait before executing its action may not be exactly the same as the interval specified by the user.
定时器开始计时,如线程,调用start()方法。定时器可以停止(之前的行动已经开始,通过调用thecancel()方法)。计时器在执行其动作之前会等待的间隔可能与用户指定的时间间隔不完全相同。
1 def hello(): 2 print("hello, world") 3 4 t = Timer(30.0, hello) 5 t.start() # after 30 seconds, "hello, world" will be printed
9.事件(Events)
An event is a simple synchronization object;
the event represents an internal flag, and threads
can wait for the flag to be set, or set or clear the flag themselves.
event = threading.Event()
# a client thread can wait for the flag to be set
event.wait()
# a server thread can set or reset it
event.set()
event.clear()
If the flag is set, the wait method doesn’t do anything.
If the flag is cleared, wait will block until it becomes set again.
Any number of threads may wait for the same event.
1 import time 2 import threading 3 4 event = threading.Event() 5 6 def lighter(): 7 count = 0 8 event.set() 9 while True: 10 if count <= 4: 11 print("