线程

1.线程定义

线程是一条执行路径,是程序执行时的最小单位,它是进程的一个执行流,是CPU调度和分派的基本单位,一个进程可以由很多个线程组成,线程间共享进程的所有资源,每个线程有自己的堆栈和局部变量。线程由CPU独立调度执行,在多CPU环境下就允许多个线程同时运行。同样多线程也可以实现并发操作,每个请求分配一个线程来处理。
一个正在运行的软件(如迅雷)就是一个进程,一个进程可以同时运行多个任务( 迅雷软件可以同时下载多个文件,每个下载任务就是一个线程), 可以简单的认为进程是线程的集合。
线程是一条可以执行的路径。多线程就是同时有多条执行路径在同时(并行)执行

总:

线程是操作系统调度的最小单位
它被包含在进程之中,是进程中的实际运作单位
进程本身是无法自己执行的,要操作cpu,必须创建一个线程,线程是一系列指令的集合

  1. 线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位
  2. 一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务
  3. 无论你启多少个线程,你有多少个cpu, Python在执行的时候会淡定的在同一时刻只允许一个线程运行
  4. 进程本身是无法自己执行的,要操作cpu,必须创建一个线程,线程是一系列指令的集合
  5. 所有在同一个进程里的线程是共享同一块内存空间的,不同进程间内存空间不同
  6. 同一个进程中的各线程可以相互访问资源,线程可以操作同进程中的其他线程,但进程仅能操作子进程
  7. 两个进程想通信,必须要通过一个中间代理
  8. 对主线程的修改可能回影响其他子线程,对主进程修改不会影响其他进程因为进程间内存相互独立,但是同一进程下的线程共享内存

2.进程和线程的关系与区别

关系:
一个程序就是一个进程,而一个程序中的多个任务则被称为线程。进程是表示资源分配的基本单位,又是调度运行的基本单位。,亦即执行处理机调度的基本单位。 进程和线程的关系:
一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。线程是操作系统可识别的最小执行和调度单位。
资源分配给进程,同一进程的所有线程共享该进程的所有资源。同一进程中的多个线程共享代码段(代码和常量),数据段(全局变量和静态变量),扩展段(堆存储)。但是每个线程拥有自己的栈段,栈段又叫运行时段,用来存放所有局部变量和临时变量,即每个线程都有自己的堆栈和局部变量。
处理机分给线程,即真正在处理机上运行的是线程。
线程在执行过程中,需要协作同步。不同进程的线程间要利用消息通信的办法实现同步。
如果把上课的过程比作进程,把老师比作CPU,那么可以把每个学生比作每个线程,所有学生共享这个教室(也就是所有线程共享进程的资源),上课时学生A向老师提出问题,老师对A进行解答,此时可能会有学生B对老师的解答不懂会提出B的疑问(注意:此时可能老师还没有对A同学的问题解答完毕),此时老师又向学生B解惑,解释完之后又继续回答学生A的问题,同一时刻老师只能向一个学生回答问题(即:当多个线程在运行时,同一个CPU在某一个时刻只能服务于一个线程,可能一个线程分配一点时间,时间到了就轮到其它线程执行了,这样多个线程在来回的切换)
区别:
1、进程包含线程
2、线程共享内存空间
3、进程内存是独立的(不可互相访问)
4、进程可以生成子进程,子进程之间互相不能互相访问(相当于在父级进程克隆两个子进程)
5、在一个进程里面线程之间可以交流。两个进程想通信,必须通过一个中间代理来实现
6、创建新线程很简单,创建新进程需要对其父进程进行克隆。
7、一个线程可以控制或操作同一个进程里面的其它线程。但进程只能操作子进程。
8、父进程可以修改不影响子进程,但不能修改。
9、线程可以帮助应用程序同时做几件事

3.线程的状态

创建(new)状态: 准备好了一个多线程的对象,即执行了new Thread(); 创建完成后就需要为线程分配内存
就绪(runnable)状态: 调用了start()方法, 等待CPU进行调度
运行(running)状态: 执行run()方法
阻塞(blocked)状态: 暂时停止执行线程,将线程挂起(sleep()、wait()、join()、没有获取到锁都会使线程阻塞), 可能将资源交给其它线程使用
死亡(terminated)状态: 线程销毁(正常执行完毕、发生异常或者被打断interrupt()都会导致线程终止)

4.多线程

4.1 多线程优点

多线程可以提高程序的效率。
实际生活案例:村长要求喜洋洋在一个小时内打100桶水,可以喜洋洋一个小时只能打25桶水,如果这样就需要4个小时才能完成任务,为了在一个小时能够完成,喜洋洋就请美洋洋、懒洋洋、沸洋洋,来帮忙,这样4只羊同时干活,在一小时内完成了任务。原本用4个小时完成的任务现在只需要1个小时就完成了,如果把每只羊看做一个线程,多只羊即多线程可以提高程序的效率。

多线程应用场景
一般线程之间比较独立,互不影响
一个线程发生问题,一般不影响其它线程

4.2 for循环同时启动多个线程

import threading
import time

def sayhi(num): #定义每个线程要运行的函数
    print("running on number:%s" %num)
    time.sleep(3)
for i in range(50):
    t = threading.Thread(target=sayhi,args=('t-%s'%i,))
    t.start()

4.3 t.join(): 实现所有线程都执行结束后再执行主线程

让当前线程加入父线程,加入后父线程会一直wait,直到子线程执行完毕后父线程才能执行。当我们调用某个线程的这个方法时,这个方法会挂起调用线程,直到被调用线程结束执行,调用线程才会继续执行。
将某个线程加入到当前线程中来,一般某个线程和当前线程依赖关系比较强,必须先等待某个线程执行完毕才能执行当前线程。一般在run()方法内使用

import threading
import time
start_time = time.time()

def sayhi(num): #定义每个线程要运行的函数
    print("running on number:%s" %num)
    time.sleep(3)
t_objs = []    #将进程实例对象存储在这个列表中
for i in range(50):
    t = threading.Thread(target=sayhi,args=('t-%s'%i,))
    t.start()          #启动一个线程,程序不会阻塞
    t_objs.append(t)
print(threading.active_count())    #打印当前活跃进程数量
for t in t_objs: #利用for循环等待上面50个进程全部结束
    t.join()     #阻塞某个程序
print(threading.current_thread())    #打印执行这个命令进程

print("----------------all threads has finished.....")
print(threading.active_count())
print('cost time:',time.time() - start_time)

4.4 setDaemon(): 守护线程,主线程退出时,需要子线程随主线程退出

程序进入主线程,运行Parent对应的线程,Parent的线程代码分两段,一段是启动一个子线程,一段是Parent线程的线程体代码,首先会将Child线程加入到Parent线程,join()方法会调用join(0)方法(join()方法是普通方法并没有加锁,join(0)会加锁),join(0)会执行while(isAlive()) { wait(0);} 循环判断线程是否处于活动状态,如果是继续wait(0)知道isAlive=false结束掉join(0), 从而结束掉join(), 最后回到Parent线程体中继续执行其它代码。
在Parent调用child.join()后,child子线程正常运行,Parent父线程会等待child子线程结束后再继续运行

import threading
import time
start_time = time.time()

def sayhi(num): #定义每个线程要运行的函数
    print("running on number:%s" %num)
    time.sleep(3)
for i in range(50):
    t = threading.Thread(target=sayhi,args=('t-%s'%i,))
    t.setDaemon(True)  #把当前线程变成守护线程,必须在t.start()前设置
    t.start()          #启动一个线程,程序不会阻塞
print('cost time:',time.time() - start_time)

5.GIL全局解释器锁:保证同一时间仅有一个线程对资源有操作权限

作用:在一个进程内,同一时刻只能有一个线程执行
说明:python多线程中GIL锁只是在CPU操作时(如:计算)才是串行的,其他都是并行的,所以比串行快很多
1)为了解决不同线程同时访问同一资源时,数据保护问题,而产生了GIL
2)GIL在解释器的层面限制了程序在同一时间只有一个线程被CPU实际执行,而不管你的程序里实际开了多少条线程
3)为了解决这个问题,CPython自己定义了一个全局解释器锁,同一时间仅仅有一个线程可以拿到这个数据
4)python之所以会产生这种不好的状况是因为python启用一个线程是调用操作系统原生线程,就是C接口
5)但是这仅仅是CPython这个版本的问题,在PyPy,中就没有这种缺陷

6.线程锁

1)当一个线程对某个资源进行CPU计算的操作时加一个线程锁,只有当前线程计算完成主动释放锁,其他线程才能对其操作
2)这样就可以防止还未计算完成,释放GIL锁后其他线程对这个资源操作导致混乱问题
用户锁使用举例

import time
import threading
lock = threading.Lock()          #1 生成全局锁
def addNum():
    global num                  #2 在每个线程中都获取这个全局变量
    print('--get num:',num )
    time.sleep(1)
    lock.acquire()              #3 修改数据前加锁
    num  -= 1                   #4 对此公共变量进行-1操作
    lock.release()              #5 修改后释放

7.Semaphore(信号量)

1.互斥锁:同时只允许一个线程更改数据,而Semaphore是同时允许一定量线程更改数据
2. 比如厕所有3个坑,那最多只允许3个人上厕所,后面的人只能等里面有人出来了才能再进去
3. 作用就是同一时刻允许运行的线程数量

8.线程池实现并发

import requests
from concurrent.futures import ThreadPoolExecutor

def fetch_request(url):
    result = requests.get(url)
    print(result.text)

url_list = [
    'https://www.baidu.com',
    'https://www.google.com/',         
    #google页面会卡住,知道页面超时后这个进程才结束
    'http://dig.chouti.com/',          
    #chouti页面内容会直接返回,不会等待Google页面的返回
]

pool = ThreadPoolExecutor(10)            
# 创建一个线程池,最多开10个线程
for url in url_list:
    pool.submit(fetch_request,url)       
    # 去线程池中获取一个线程,线程去执行fetch_request方法

pool.shutdown(True)                      
# 主线程自己关闭,让子线程自己拿任务执行
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值