python基础⑬-多线程

系列文章目录


python基础①-基础环境搭建和工具使用
python基础②-常用的各种数据类型初认知
python基础③-数据类型常用的操作和方法字符串、数值、bool
python基础④-数据类型常用的操作和方法列表、元组、字典、集合
python基础⑤-控制流程
python基础⑥-函数
python基础⑦-字符编码与文件操作
python基础⑧-异常
python基础⑨-迭代器和生成器
python基础⑩-面向对象
python基础⑪-继承与派生
python基础⑫-多进程
python基础⑬-多线程
python基础⑭-进程池、线程池、协程
python基础⑮-网络编程socket
python基础⑯-网络编程socket进阶

什么是线程

1 什么是线程
进程其实一个资源单位(开启一个内存空间,里面放应该执行的代码,代码运行产生的数据),
而进程内的线程才是cpu上的执行单位
进程是资源单位 qq资源 相当于一个车间 造发动机和造轮胎能共享吗
线程其实指的就是代码的执行过程(开空间没关系,数据往进程去要)
线程就是车间流水线 跟车间要
至少有一个线程 一个车间也可以有多少流水线
2 为何要用线程
线程vs进程
1. 同一进程下的多个线程共享该进程内的资源
2. 创建线程的开销要远远小于进程
并发2种
多进程实际上是每个进程里面单独一个线程
由于进程当中资源不共享
并发多个任务需要通信需要利用管道或者队列
多线程是指同一个进程里面多个线程
本身同一个进程里面多个线程资源就共享
所以不需要借助任何的机制,数据之间就可以交互
3 如何用线程

线程

开启线程

from threading import Thread
import time

def task(name):
    print('%s is running'%name) # 2
    time.sleep(2)
    print('%s is done'%name)

if __name__ == '__main__':
    t=Thread(target=task,args=('线程1',))
    # 造线程非常快,因为不用开辟空间了
    t.start()
    print('主') # 1

在这里插入图片描述

线程特性

线程的物理空间(内存)可以共享

from threading import Thread,active_count,current_thread
import time,os
n = 100
def task():
    global n
    print('%s is running'%os.getpid())
    print('子%s'%current_thread().name)
    n = 0
    time.sleep(3)
if __name__ == '__main__':
    t=Thread(target=task)
    t.start()
    # 等待子线程运行完
    t.join()
    # 线程的个数
    print(active_count())
    print(n)
    # 线程所在的进程pid
    print('主%s'%os.getpid())
    # 线程的名字
    print('主%s'%current_thread().name)

在这里插入图片描述

守护线程

所以守护线程需要等到非守护的所有线程都死了才死
主线程就是一个守护线程

主线程掌管了这个进程里面的资源
主线程不会死了,资源是来自主线程的
主线程是这个进程里面的老大
等待所有的子线程死了才死

# 主线程要等到所有的子线程死了才死
from threading import Thread
import time


def task(name):
    print('%s is running' % name)  # 2
    time.sleep(2)
    print('%s is done' % name)


if __name__ == '__main__':
    t = Thread(target=task, args=('线程1',))
    # 造线程非常快,因为不用开辟空间了
    # t.daemon = True
    t.start()
    print('主')  # 1

在这里插入图片描述


from threading import Thread
from multiprocessing import Process
import time

def foo():
    print(123)
    time.sleep(1)
    print('end123')
def bar():
    print(456)
    time.sleep(3)
    print('end456')
if __name__ == '__main__':
    # # 进程
    # t1=Process(target=foo)
    # t2=Process(target=bar)
    # t1.daemon = True
    # # # 创建进程的开销非常大,看你机器的配置,配置好123看得到
    # t1.start()
    # t2.start()
    # print('主')
    # 线程
    t1 = Thread(target=foo)
    t2 = Thread(target=bar)
    t1.daemon = True

    t1.start()
    t2.start()
    print('主')
    # 因为456线程没有死掉
    # 守护线程 》》主线程 》》非守护线程
    # 按时下班的员工》》老板 》》加班的员工

在这里插入图片描述

线程互斥锁

为什么要有互斥锁

当多个线程访问共享资源(例如下面访问同一个n)可能会出现线程并发问题
原因是因为cpu可能随时切换任务,多个指令如果没有原子性保障就可能出现线程并发安全

from threading import Thread,Lock
import time
mutex=Lock()
n = 100
def task():
    global n
    # 线程1加锁
    # mutex.acquire()
    temp = n
    # 在这个时间消耗完之前,后面的99个线程都进来了
    # 并且拿到的是temp=100
    # 效率高了,不安全
    # IO操作切换线程
    time.sleep(0.1)
    n = temp-1
    # 线程1计算完释放锁
    # mutex.release()
if __name__ == '__main__':
    t_1 = []
    start = time.time()
    for i in range(100):
        t = Thread(target=task)
        t_1.append(t)
        t.start()
    for t in t_1:
        t.join()
    print(n,time.time()-start)

此处就导致了n最后为99。理论上应该为0
在这里插入图片描述

怎么解决并发问题

from threading import Thread, Lock
import time

mutex = Lock()
n = 100


def task():
    global n
    # 线程1加锁
    mutex.acquire()
    temp = n
    # 在这个时间消耗完之前,后面的99个线程都进来了
    # 并且拿到的是temp=100
    # 效率高了,不安全
    # IO操作切换线程
    time.sleep(0.1)
    n = temp - 1
    # 线程1计算完释放锁
    mutex.release()


if __name__ == '__main__':
    t_1 = []
    start = time.time()
    for i in range(100):
        t = Thread(target=task)
        t_1.append(t)
        t.start()
    for t in t_1:
        t.join()
    print(n, time.time() - start)

mutex.acquire()
mutex.release()
这两个方法中间的代码永远保证只有一个线程执行到。如果执行的线程没执行完。其他线程是没法进来执行的。从而保证了原子性。保证了 线程安全
在这里插入图片描述

GIL 全局解释器锁

  1. 什么是GIL(全局解释器锁)
    互斥锁就是把多个任务的共享数据的修改由并发变成串行
    代码运行先拿到cpu的权限,还需要把代码丢给解释器,再在进程里面的线程运行
    GIL本质就是一把互斥锁,相当于执行权限
    每个进程内都会存在一把GIL,同一进程内的多个线程
    必须抢到GIL之后才能使用解释器来执行自己的代码,
    即同一进程下的多个线程无法实现并行,
    用不了多核(多个cpu)优势
    但是可以实现并发
    因为多线程是遇到io操作就会释放GIL锁
  2. 为何要有GIL
    垃圾回收机制不是线程安全的
    每个进程内都会存在一把GIL
    意味着有锁才能计算
    多进程适合处理计算密集型
    多线程适合处理io密集型 所以多线程多核优势没有意义
  3. 如何用GIL
    有了GIL,应该如何处理并发

也就是说如果没有io操作的话。同一个进程里面开启多个线程没有意义。因为一个线程拿到了GIL锁后会一直执行完所有的计算型操作后才会释放锁
跟java中的多线程不太一样

计算密集型:应该使用多进程

from multiprocessing import Process
from threading import Thread
import os, time


def work():
    res = 0
    for i in range(10000000):
        res *= i
if __name__ == '__main__':
    l = []
    # 看到cpu个数
    # print(os.cpu_count())
    start = time.time()
    for i in range(8):
        # 多进程8个cpu同时在算,计算效率高,但是进程之间切换效率低
        # p = Process(target=work)
        #         # 多线程是1个cpu在计算
        #         # 毕竟计算效率低,但是切换效率高
        p = Thread(target=work)
        l.append(p)
        p.start()
    for p in l:
        p.join()
    print('主%s' % (time.time() - start))

IO密集型: 应该开启多线程

from multiprocessing import Process
from threading import Thread
import os,time

def work():
    time.sleep(2)

if __name__ == '__main__':

    l = []
    start = time.time()
    for i in range(20):
        # p = Process(target=work)

        p = Thread(target=work)
        l.append(p)
        p.start()
    for p in l:
        p.join()
    print('主%s'%(time.time()-start))

线程queue

# 虽然线程中数据共享,但是队列可以处理锁的问题
import queue

q = queue.Queue(3)

q.put(1)
q.put(2)
q.put(3)
# q.put(4)

print(q.get())
print(q.get())
print(q.get())
print(q.get())

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

七层汉堡王

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值