python学习笔记 day38 线程

1. 线程 

线程是CPU调度的最小单位,进程是CPU分配资源的最小单位;

每个进程中至少有一个线程;(所以进程是包含线程的,同一进程的不同线程之间数据是共享的);

开启进程的时间要比开启线程的时间长,CPU在进程之间的切换比在线程之间的切换要慢很多;

如果有两个任务需要共享内存,有需要实现异步,就需要开启多线程;

如果两个任务需要数据隔离--就需要开启多进程;

多线程的优点:

1. 轻量级;

2. 同一个进程的多个线程之间的数据是共享的;

进程与线程之间的区别:

1. 地址空间和其他资源(如打开文件):进程间相互独立,同一进程的多线程之间共享数据资源,某进程内的线程在其他进程不可见;

2. 通信:进程间通信IPC(比如队列,管道),线程之间可以直接读写线程数据段(如全局变量)来进行通信;

3. 调度和切换:线程上下文切换,比进程上下文切换要快得多;

4. 在多线程操作系统中,进程不是一个可执行的实体;(因为线程是CPU调度任务的最小单位,进程只是CPU资源分配的最小单位)

2. GIL--全局解释器锁

其实python在多线程上并没有实现真正的多并发,python的解释器加了一把锁,使得同一时间只有一个线程被CPU执行(实际上是为了保证数据的安全);

如果是在高计算(不涉及IO操作,阻塞的)可以开多进程来实现;

如果是在高IO(input print 文件的读写,网络通信accept recv等)可以开多线程(比如在网络爬虫,其实IO操作等待的时间是很长的,即使真的有多线程并发,也是需要等的,所以这种情况下多线程并发执行并不会带来效率的提升)

 3. 简单的开启多线程

# 效果就是先打印主线程的进程id然后睡一秒之后同时打印10个线程的执行结果,可以看出10个线程之间是异步的,但是其实并不是真正的高并发;
# 而且如果子线程中执行代码不睡一秒,那么打印顺序是先打印10个线程的执行结果,然后才打印主线程,就是因为开启线程的时间太短了
from threading import Thread
import time
import os
def func(i):
    time.sleep(1)   # 如果子线程这里不睡一秒,那么就是先打印子线程执行结果,然后才打印主线程的,因为开启线程的时间真的很短,还没执行到主线程的打印,开启子线程 就已经出结果了
    print("%s-hello,xuanxuan-%s"%(i,os.getpid()))


# 开多线程时不用写在if __name__=="__main__":中
for i in range(10):
    t=Thread(target=func,args=(i,))   # 创建10个线程对象
    t.start()  # 开启线程,子线程是异步并发的*(但是python的多线程并不是真正意义上的并发,因为有GIL全局解释器锁)
print(os.getpid())  # 其实主线程跟开的10个子线程之间也是异步的

运行结果:


 如果是想让主线程和多个子线程之间同步(也就是想先让多个子线程执行完毕,然后在继续执行主线程的代码):可以使用join()方法,但是注意要把开的多个线程放在一个列表中,最后统一使用join()方法关闭(这样才能实现多个线程之间的异步并发效果)否则开启一个线程就join()一下 这样多个线程之间就变为同步执行;

from threading import Thread
import time
import os
def func(i):
    time.sleep(1)   # 如果子线程这里不睡一秒,那么就是先打印子线程执行结果,然后才打印主线程的,因为开启线程的时间真的很短,还没执行到主线程的打印,开启子线程 就已经出结果了
    print("%s-hello,xuanxuan-%s"%(i,os.getpid()))


# 开多线程时不用写在if __name__=="__main__":中
t_lst=[]   # 存放开启的多个线程
for i in range(10):
    t=Thread(target=func,args=(i,))   # 创建10个线程对象
    t.start()  # 开启线程,子线程是异步并发的*(但是python的多线程并不是真正意义上的并发,因为有GIL全局解释器锁)
    t_lst.append(t)
[t.join() for t in t_lst]  # 最后统一关闭多个线程,仍然保证多个线程之间异步并发
print(os.getpid())  # 使用join()方法之后,主线程和多个子线程之间就变为同步,就是主线程得先等待子线程执行完毕,然后才执行主线程中的代码

运行结果:

 4. 开启线程的另一种方式----使用类

from threading import Thread
import os
class MyThread(Thread):   # 自定义的类必须继承自Thread类
    def run(self):  # 必须在类内实现run()方法,线程创建之后 一旦执行t.start() 实质上就是执行类中的run()方法
        print("hello,xuanxuan-%s"%os.getpid())

for i in range(10):
    t=MyThread()   # 创建10个线程
    t.start()
print("主线程:%s"%os.getpid())

运行结果:


 1. 如果在主线程中开了多个子线程,想统计开了多少个子线程:

from threading import Thread
import os

class MyThread(Thread):
    _count=0   # 在类内创建一个静态属性(类在定义时就会被执行,相当于初始化,调用时不会再被执行了)---这个属性在所有的线程中共享的,因为线程之间本来就是共享数据的
    def run(self):  # 每当一个线程开启时 t.start()方法实际上会去执行类中的run()方法
        MyThread._count+=1   # 每次开启一个线程都会执行run()方法,也就是类的静态属性_count自加1
        print("hello,xuanxuan-%s"%os.getpid())

for i in range(10):
    t=MyThread()  # 创建10个线程(虽然这里已经知道是创建了10个线程,但是就是想使用类的静态属性,在run()方法中操作,获取线程的个数)
    t.start()     # 开启线程,会自动执行类MyThread类的run()方法,也就是每开一个线程都会使得MyThread的_count加1
print("主线程:%s"%os.getpid())
print("主线程中开启的子线程数目:",t._count)  # 拿着进程对象取类的静态属性_count的值(其实所有线程的_count都是10)

 运行结果:

 2. 如果想子线程执行的函数,需要参数,应该在类中怎么实现:(在类中继承父类的__init__()方法:super().__init__(),然后再写派生属性)

from threading import Thread
import os
import time
import random

class MyThread(Thread):
    _count=0   # 在类内创建一个静态属性(类在定义时就会被执行,相当于初始化,调用时不会再被执行了)---这个属性在所有的线程中共享的,因为线程之间本来就是共享数据的
    def __init__(self,arg1,arg2):   # 子线程执行函数run()时,如果想传一个参数:
        super().__init__()  # 不能重写__init__()方法,必须继承自父类Thread的__init__() 然后再写派生方法
        self.arg1=arg1
        self.arg2=arg2
    def run(self):  # 每当一个线程开启时 t.start()方法实际上会去执行类中的run()方法
        MyThread._count+=1   # 每次开启一个线程都会执行run()方法,也就是类的静态属性_count自加1
        time.sleep(random.random())
        print("%s-%s-%s"%(os.getpid(),self.arg1,self.arg2))
t_lst=[]   # 想把开启的子线程都放在列表中,然后统一t.join()是为了保证开启的多个子线程仍然是异步的
           # 只不过是想在子线程执行完毕之后,执行主线程的代码(只是主线程和多个子线程之间同步,多个子线程之间仍然是异步并发的)
for i in range(10):
    t=MyThread(i,i*"*")  # 创建10个线程(可以传参的)(虽然这里已经知道是创建了10个线程,但是就是想使用类的静态属性,在run()方法中操作,获取线程的个数)
    t.start()     # 开启线程,会自动执行类MyThread类的run()方法,也就是每开一个线程都会使得MyThread的_count加1
    t_lst.append(t)
[t.join() for t in t_lst]  # 主线程会等待子线程执行完毕,才会继续执行下面的代码,但是子线程之间仍然是异步并发的
print("主线程:%s"%os.getpid())
print("主线程中开启的子线程数目:",t._count)  # 拿着进程对象取类的静态属性_count的值(其实所有线程的_count都是10)

运行结果:

 3. 最后介绍在第一种开启线程的方法的一些其他的方法:

import threading
import time

def func():
    time.sleep(1)
    print("hello,xuanxuan-线程名:%s-线程id: %s"%(threading.current_thread().name,threading.current_thread().ident))
     #  threading.current_thread().name----返回当前线程名  threading.current_thread().ident----返回当前线程id

for i in range(10):
    t=threading.Thread(target=func)  # 创建10个线程
    t.start()  # 开启线程

print(threading.enumerate())  # 打印当前正在执行的线程列表,包括主线程和开启的所有子线程
print("正在执行的线程数:",len(threading.enumerate()))  # 打印正在执行的线程数
print("正在执行的线程数:",threading.active_count())   # 也是打印正在执行的线程数

 运行结果:

猜你喜欢

转载自www.cnblogs.com/xuanxuanlove/p/9787354.html