1 进程和线程是操作系统的基本概念,计算机是由硬件和软件组成。硬件中的CPU是计算机的核心,他承担计算机的所有任务。 2 操作系统是运行在硬件上的软件,是计算机的管理者,他负责资源的管理和分配、任务的调度。 3 程序是运行在系统上的具有某种功能的软件,比如浏览器、音乐、软件等。 4 每次执行程序的时候,都会完成一定的功能,比如说浏览器帮我们打开网页,为了保证其独立性,就需要一个专门的管理和控制执行程序 5 的数据结构--进程控制块。 6 进程就是一个程序在一个数据集上的一次动态执行过程。进程一般由程序、数据集、进程控制块三部分组成。 7 我们编写的程序用来描述进程要完成哪些功能以及如何完成;数据集则是程序在执行过程中所需要使用的资源;进程控制块用来记录进程的 8 外部特征,描述进程的执行变化过程,系统可以利用他来控制和管理进程,他是系统感知进程存在的唯一标志。 9 线程是操作系统能够进行运算调度的最小单位。他被包含在进程之中,是进程中的实际运作单位。 10 一个线程指的是进程中一个单一顺序的控制流。 11 一个进程中可以并发多个线程,每条线程并行执行不同的任务。 12 13 1.进程是不活泼的,进程从来不执行任何东西,他只是线程的容器,若要是进程完成某种操作,他必须有一个在他环境中运行的线程,此线程负责 14 执行包含在进程地址空间中的代码。 15 2.创建一个进程时,操作系统会自动创建这个进程的第一个线程,称为主线程。此后该线程可以创建其他的线程。 16 3.线程与进程的关系:线程是属于进程的,线程运行在进程空间内,同一进程所产生的线程共享同一内存空间,当进程退出时,该进程产生的 17 线程都会被强制清楚并退出。线程可与同一进程的其他线程共享进程所拥有的全部资源,但是其本身基本上不拥有系统资源,只拥有一点在运行中必不可少的信息(如程序计数器、一组寄存器和栈)。 18 19 线程与进程的区别: 20 进程:对各种资源管理的集合; 21 线程:操作系统最小的调度单位,是一串指令的集合。 22 23 进程中第一个线程是主线程,主线程创建其他线程,其他线程也可以创建线程,线程之间是平等的 24 进程有父进程、子进程,独立的内存空间,唯一的进程标识符pid 25 26 启动线程比启动进程快。运行进程和运行线程速度上是一样的,没有可比性。 27 线程共享内存空间,进程的内存是独立的 28 29 父进程生成子进程,相当于克隆一份内存空间。进程之间不能直接访问 30 创建新线程很简单,创建新进程需要对其父进程进行一次克隆 31 一个线程可以控制和操作同一进程里的其他线程,但是进程只能操作子进程 32 33 同一个进程之间的线程之间可以直接通信 34 两个进程想通信必须通过一个中间代理来实现 35 36 进程的特性: 37 动态性:进程的实质是程序的一次执行过程,进程是动态产生、动态消亡的 38 并发性:任何进程都可以同其他进程一起并发执行 39 独立性:进程是一个能独立运行的基本单位,同时也是系统分配资源和调度的独立单位 40 异步性:每个进程都以相互独立、不可预知的速度向前推进 41 42 进程由程序、数据和进程控制块三部分组成 43 44 多任务的实现有3种方式: 45 多进程模式 46 多线程模式 47 多进程+多线程模式 48 49 50 # 直接调用 51 import threading 52 import time 53 def run(n): 54 # time.sleep(2) 55 print('task', n) 56 t1=threading.Thread(target=run,args=('t1',)) 57 t2=threading.Thread(target=run,args=('t2',)) 58 t1.start() 59 t2.start() 60 61 # task t1 62 # task t2 63 64 65 继承式调用 66 import threading 67 class MyThread(threading.Thread): 68 def __init__(self,n): 69 super(MyThread,self).__init__() 70 self.n=n 71 def run(self): 72 print('这种方式函数名必须是run,写死的',self.n) 73 t1=MyThread('t1') 74 t2=MyThread('t2') 75 t1.start() 76 t2.start() 77 78 # 这种方式函数名必须是run,写死的 t1 79 # 这种方式函数名必须是run,写死的 t2 80 81 82 83 使用传统编程看执行任务花费的时间 84 import time 85 import threading 86 87 def task1(): 88 time.sleep(5) 89 print('任务一完成',time.ctime()) 90 def task2(): 91 time.sleep(5) 92 print('任务二完成',time.ctime()) 93 print('执行任务前打印当前时间',time.ctime()) 94 task1() 95 task2() 96 print('执行结束,记录结束时间',time.ctime()) 97 98 # 执行任务前打印当前时间 Sun Mar 10 08:21:49 2019 99 # 任务一完成 Sun Mar 10 08:21:54 2019 100 # 任务二完成 Sun Mar 10 08:21:59 2019 101 # 执行结束,记录结束时间 Sun Mar 10 08:21:59 2019 102 103 104 105 采用多线程实行并行处理,查看执行同样任务所花费的时间 106 import time 107 import threading 108 109 def task1(): 110 time.sleep(3) 111 print('任务一完成',time.ctime()) 112 def task2(): 113 time.sleep(3) 114 print('任务二完成',time.ctime()) 115 print('执行任务前打印当前时间',time.ctime()) 116 t1=threading.Thread(target=task1) 117 t2=threading.Thread(target=task2) 118 t1.start() 119 t2.start() 120 t1.join() 121 t2.join() 122 print('执行结束,打印当前时间') 123 124 # 执行任务前打印当前时间 Sun Mar 10 08:28:44 2019 125 # 任务二完成 任务一完成 Sun Mar 10 08:28:47 2019 126 # Sun Mar 10 08:28:47 2019 127 # 执行结束,打印当前时间 128 129 130 131 任何进程默认就会启动一个线程,我们把该线程称为主线程,主线程又可以启动新的线程,Python的threading模块有个current_thread()函数, 132 他永远返回当前线程的实例。主线程实例的名字叫MainThread,子线程的名字在创建线程实例时指定,这里用hello命名子线程。 133 名字仅仅用来在打印时显示,完全没有其他意义,如果不起名字,python就会自动给线程命名为Thread-1,Thread-2... 134 import time 135 import threading 136 137 def loop(): 138 print('线程%s正在执行。。。'%threading.current_thread().name) 139 n=0 140 while n<5: 141 n+=1 142 print('线程%s>>>%s'%(threading.current_thread().name,n)) 143 time.sleep(2) 144 print('线程%s结束'%threading.current_thread().name) 145 print('主线程%s正在执行。。。'%threading.current_thread().name) 146 # 给线程起名hello 147 t=threading.Thread(target=loop,name='hello') 148 t.start() 149 t.join() 150 print('主线程%s结束'%threading.current_thread().name) 151 152 # 主线程MainThread正在执行。。。 153 # 线程hello正在执行。。。 154 # 线程hello>>>1 155 # 线程hello>>>2 156 # 线程hello>>>3 157 # 线程hello>>>4 158 # 线程hello>>>5 159 # 线程hello结束 160 # 主线程MainThread结束 161 162 163 164 当我们使用setDaemon(True)方法时,设置子线程为守护线程时,主线程一旦执行结束,则全部线程全部被终止执行, 165 可能出现的情况就是,子线程的任务还没有完全结束,就被迫停止。不设置的话默认为setDaemon(False), 166 如果你设置一个线程为守护线程,就表示这个线程是不重要的,在进程退出的时候,不用等待这个线程退出。 167 import time 168 import threading 169 170 def task(): 171 print('start fun',time.ctime()) 172 time.sleep(2) 173 print('end fun',time.ctime()) 174 t1=threading.Thread(target=task) 175 print('线程名字',t1.getName(),time.ctime()) #显示实例线程名字 176 t1.setDaemon(True) # 设置t1为守护线程 177 t1.start() 178 time.sleep(1) 179 print(threading.current_thread().name,time.ctime()) # 主线程执行结束,则全部终止 180 181 # 线程名字 Thread-1 Sun Mar 10 16:58:14 2019 182 # start fun Sun Mar 10 16:58:14 2019 183 # MainThread Sun Mar 10 16:58:15 2019 184 185 186 187 188 多进程与多线程最大的不同在于,多进程中,同一个变量,各自有一份拷贝在每个进程中,互不影响,而多线程中,所有变量都由所有线程 189 共享。所以任何一个变量都可以被任何一个线程修改,因此,线程之间共享数据最大的危险在于多个线程同时修改一个变量,把内容给改乱了。 190 191 192 193 # 没有锁 194 import threading 195 196 balance=0 197 def task(n): 198 global balance 199 balance+=n 200 balance-=n 201 def task2(arg,n): 202 while arg>0: 203 # lock.acquire()#获得锁 204 task(n) 205 arg-=1 206 207 t1=threading.Thread(target=task2,args=(880000,5)) 208 t2=threading.Thread(target=task2,args=(970000,6)) 209 t1.start() 210 t2.start() 211 t1.join() 212 t2.join() 213 print(balance) 214 215 216 217 import threading 218 219 balance=0 220 lock=threading.RLock() 221 def task(n): 222 global balance 223 balance+=n 224 balance-=n 225 def task2(arg,n): 226 while arg>0: 227 lock.acquire()#获得锁 228 try: 229 task(n) 230 finally: 231 lock.release() 232 arg-=1 233 234 t1=threading.Thread(target=task2,args=(1880000,5)) 235 t2=threading.Thread(target=task2,args=(1770000,6)) 236 t1.start() 237 t2.start() 238 t1.join() 239 t2.join() 240 print(balance) 241 242 当多个线程同时执行lock.acquire()时,只有一个线程能够成功地获取锁,然后执行代码,其他线程就继续等待直到获得锁为止。 243 获得锁的线程用完后一定要释放锁,否则那些苦苦等待的线程将永远等待下去,称为死线程。 244 锁的好处就是确保了某段代码只能由一个线程从头到尾的执行。坏处当然也很多,首先是阻止了多线程并发执行,包含锁的某段代码实际上 245 只能以单线程的模式执行,效率就大大下降了。其次,由于可以存在多个锁,不同的线程持有不同的锁,并试图获取对方持有的锁时,可能会 246 造成死锁,导致多个线程全部挂起,既不能执行,也无法结束,只能靠操作系统强制终止。 247 248 在python中,不能利用多线程实现多核任务,但可以通过多进程实现多核任务。 249 250 import time 251 import threading 252 253 globals_num=0 254 #lock=threading.RLock() 255 def Func(): 256 # lock.acquire() 257 global globals_num 258 globals_num+=1 259 time.sleep(1) 260 print(globals_num) 261 # lock.release() 262 for i in range(10): 263 t=threading.Thread(target=Func) 264 t.start() 265 266 # 1010 267 # 268 # 10 269 # 101010 270 # 271 # 10 272 # 273 # 101010 274 275 276 277 import time 278 import threading 279 280 globals_num=0 281 lock=threading.RLock() 282 def Func(): 283 lock.acquire() 284 global globals_num 285 globals_num+=1 286 time.sleep(1) 287 print(globals_num) 288 lock.release() 289 for i in range(10): 290 t=threading.Thread(target=Func) 291 t.start() 292 293 # 1 294 # 2 295 # 3 296 # 4 297 # 5 298 # 6 299 # 7 300 # 8 301 # 9 302 # 10 303 304 在多线程环境下,每个线程都有自己的数据。一个线程使用自己的局部变量比使用全局变量好,因为局部变量只有线程自己能看见,不会影响 305 其他线程,而全局变量的修改必须加锁。 306 307 ThreadLocal,很多地方叫做线程本地变量,也有些地方叫做线程本地存储,其实意思差不多。ThreadLocal为变量在每个线程中都创建了 308 一个副本,那么每个线程可以访问自己内部的副本变量。 309 ThreadLocal最常用的地方就是为每个线程绑定一个数据库连接,HTTP请求,用户身份信息等,这样一个线程的所有调用到的处理函数都可以 310 非常方便地访问这些资源。 311 312 在新的线程中local_data并没有x属性,并且在新线程中的赋值并不会影响到其他线程。 313 去掉local_data=Widgt()的注释,local_data就变成了线程共享的变量。 314 315 import threading 316 317 class Widgt(object): 318 pass 319 320 def test(): 321 local_data=threading.local() 322 # local_data=Widgt() 323 local_data.x=1 324 def thread_func(): 325 print('Has x in new thread:%s'%hasattr(local_data,'x'))#hasattr(实例名,属性名)判断对象中是否存在该属性 326 local_data.x=2 327 print('Has x in new thread:%s'%hasattr(local_data,'x')) 328 print('x in pre thread is %s'%local_data.x) 329 t=threading.Thread(target=thread_func) 330 t.start() 331 t.join() 332 print('x in pre thread is %s'%local_data.x) 333 if __name__=='__main__': 334 test() 335 336 # Has x in new thread:False 337 # Has x in new thread:True 338 # x in pre thread is 2 339 # x in pre thread is 1 340 341 342 343 344 创建全局ThreadLocal对象 345 import threading 346 347 local_school=threading.local() 348 def process_student(): 349 #获取当前线程关联的student 350 std=local_school.student 351 print('Hello,%s(in %s)'%(std,threading.current_thread().name)) 352 def process_thread(name): 353 #绑定ThreadLocal的student 354 local_school.student=name 355 process_student() 356 t1=threading.Thread(target=process_thread,args=('Alice',),name='Thread-A') 357 t2=threading.Thread(target=process_thread,args=('Bob',),name='Thread-B') 358 t1.start() 359 t2.start() 360 t1.join() 361 t2.join() 362 363 # Hello,Alice(in Thread-A) 364 # Hello,Bob(in Thread-B) 365 366 367 368 multiprocessing模块提供了一个Process类来代表一个进程对象,multiprocessing模块就是跨平台版本的多进程模块。 369 import time 370 import multiprocessing 371 372 def add(number,value,lock): 373 lock.acquire() 374 try: 375 print('init add{0} number={1}'.format(value,number)) 376 for i in range(1,6): 377 number+=value 378 time.sleep(1) 379 print('add{0} number={1}'.format(value,number)) 380 except Exception as e: 381 raise e 382 finally: 383 lock.release() 384 if __name__=='__main__': 385 lock=multiprocessing.Lock() 386 number=0 387 p1=multiprocessing.Process(target=add,args=(number,1,lock)) 388 p2=multiprocessing.Process(target=add,args=(number,3,lock)) 389 p1.start() 390 p2.start() 391 print('main end') 392 393 # main end 394 # init add1 number=0 395 # add1 number=1 396 # add1 number=2 397 # add1 number=3 398 # add1 number=4 399 # add1 number=5 400 # init add3 number=0 401 # add3 number=3 402 # add3 number=6 403 # add3 number=9 404 # add3 number=12 405 # add3 number=15 406 407 408 409 410 下面的例子演示了启动一个子进程并等待其结束 411 412 from multiprocessing import Process 413 import os 414 415 #子进程要执行的代码 416 def run_proc(name): 417 print('Run child process %s(%s)...'%(name,os.getpid())) 418 if __name__=='__main__': 419 print('Parent process %s.'%(os.getpid())) 420 p=Process(target=run_proc,args=('test',)) 421 print('Child process will start.') 422 p.start() #启动进程 423 p.join() #等待子进程执行结束后再往下执行,通常用于进程间的同步 424 print('Child process end.') 425 426 # Parent process 6012. 427 # Child process will start. 428 # Run child process test(8724)... 429 # Child process end. 430 431 432 433 434 435 Pool类 在使用Python进行系统管理时,特别是同时操作多个文件目录或者远程控制多台主机,并行操作可以节约大量的时间。如果操作 436 对象数目不大时,还可以直接使用Process类动态的生成多个进程,十几个还好,但是如果上百个甚至更多,那手动去限制进程数量就显得 437 特别的繁琐,此时进程池就派上用场了。 438 Pool类可以提供指定数量的进程共用户调用,当有新的需求提交到Pool中时,如果池还没满,就会创建一个新的进程来执行请求。如果 439 池满,请求就会告知等待,直到池中有进程结束,才会创建新的进程来执行这些请求。 440 下面介绍multiprocessing模块下的Pool类下的几个方法 441 apply() 442 apply(func[,args=()[,kwds={}]])该函数用于传递不定参数,主进程会被阻塞直到函数执行结束(不建议使用,并且3.x以后不再出现) 443 apply_async() 444 apply_async(func[,args=()[,kwds={}[,callback=None]]])与apply用法一样,但他是非阻塞且支持结果返回进行回调 445 map() 446 map(func,iterable[,chunksize=None]) Pool类中的map方法,与内置的map函数用法基本一致,它会使进程阻塞直到返回结果。注意:第二个 447 参数虽然是迭代器,但在实际使用中,必须在整个队列都就绪后,程序才会运行子进程。 448 close()关闭进程池,使其不再接收新的任务。 449 terminate()结束工作进程,不再处理未完成的任务。 450 join()主进程阻塞等待子进程的退出,join方法必须在close或terminate之后使用。 451 452 from multiprocessing import Pool 453 import os 454 import time 455 import random 456 457 def long_time_task(name): 458 print('运行任务%s(%s)...'%(name,os.getpid()),time.ctime()) # os.getpid()获得当前进程的进程号 459 start=time.time() 460 time.sleep(random.random()*3) 461 end=time.time() 462 print('任务%s运行%0.2f秒'%(name,(end-start)),time.ctime()) 463 if __name__=='__main__': 464 print('Parent process %s.'%os.getpid(),time.ctime()) 465 p=Pool(4) # 创建拥有4个进程数量的进程池 466 for i in range(5): 467 p.apply_async(long_time_task,args=(i,)) 468 print('Waiting for all subprocesses done...',time.ctime()) 469 p.close() 470 p.join() 471 print('All subprocesses done.',time.ctime()) 472 473 # Parent process 9704. Mon Mar 11 16:14:31 2019 474 # Waiting for all subprocesses done... Mon Mar 11 16:14:31 2019 475 # 运行任务0(5832)... Mon Mar 11 16:14:31 2019 476 # 运行任务1(7004)... Mon Mar 11 16:14:31 2019 477 # 运行任务2(6956)... Mon Mar 11 16:14:31 2019 478 # 运行任务3(1632)... Mon Mar 11 16:14:31 2019 479 # 任务3运行1.00秒 Mon Mar 11 16:14:32 2019 480 # 运行任务4(1632)... Mon Mar 11 16:14:32 2019 481 # 任务1运行1.98秒 Mon Mar 11 16:14:33 2019 482 # 任务2运行2.36秒 Mon Mar 11 16:14:34 2019 483 # 任务0运行2.98秒 Mon Mar 11 16:14:34 2019 484 # 任务4运行2.01秒 Mon Mar 11 16:14:34 2019 485 # All subprocesses done. Mon Mar 11 16:14:35 2019 486 487 488 489 490 Process之间肯定是需要通信的,操作系统提供了很多机制来实现进程间的通信。Python的multiprocessing模块包装了底层的机制, 491 提供了Queue,Pipes等多种方式来交换数据。我们以Queue为例,在父进程中创建两个子进程,一个往Queue里写数据,另一个从Queue里 492 读取数据。 493 494 from multiprocessing import Process,Queue 495 import os 496 import time 497 import random 498 499 # 写数据进程执行的代码 500 def write(q): 501 print('Process to write:%s'%os.getpid()) 502 for value in ['A','B','C']: 503 print('Put %s to queue...'%value) 504 q.put(value) 505 time.sleep(random.random()) 506 # 读数据进程执行的代码 507 def read(q): 508 print('Process to read:%s'%os.getpid()) 509 while True: 510 value=q.get(True) 511 print('Get %s from queue.'%value) 512 if __name__=='__main__': 513 q=Queue() # 父进程创建Queue,并创建给子进程 514 w=Process(target=write,args=(q,)) 515 r=Process(target=read,args=(q,)) 516 w.start() # 启动子进程pw,写入 517 r.start() # 启动子进程pr,读取 518 w.join() 519 r.terminate() # r 520 521 # Process to write:9048 522 # Put A to queue... 523 # Process to read:1580 524 # Get A from queue. 525 # Put B to queue... 526 # Get B from queue. 527 # Put C to queue... 528 # Get C from queue. 529 530 531 532 533 Queue是python标准库中的线程安全的队列(FIFO)实现,提供了一个适用于多线程编程的先进先出的数据结构,即队列,用来在生产者 534 和消费者线程之间的信息传递。基本FIFO队列 class queue.Queue(maxsize=0) 535 FIFO即First in First out ,先进先出。Queue提供了一个基本的FIFO容器,使用方法很简单,maxsize是一个整数,指明了队列中能存放的 536 数据个数的上限。一旦达到了上限,插入会导致阻塞,直到队列中数据被消费掉。如果maxsize小于或者等于0,队列大小没有限制。 537 538 import queue 539 540 q=queue.Queue() 541 for i in range(5): 542 q.put(i) 543 while not q.empty(): 544 print(q.get()) 545 546 # 0 547 # 1 548 # 2 549 # 3 550 # 4 551 552 553 554 LIFO队列 即 last in first out ,后进先出 555 class queue.LifoQueue(maxsize=0) 556 557 import queue 558 559 q=queue.LifoQueue(maxsize=0) 560 for i in range(5): 561 q.put(i) 562 while not q.empty(): 563 print(q.get()) 564 565 # 4 566 # 3 567 # 2 568 # 1 569 # 0