python 并发编程之多线程

一、线程理论

1.什么是线程

多线程(即多个控制线程)的概念是,在一个进程中存在多个线程,多个线程共享该进程的地址空间,相当于一个车间内有多条流水线,都共用一个车间的资源。

所以,进程只是用来把资源集中到一起(进程只是一个资源单位,或者说资源集合),而线程才是cpu上的执行单位。

2.进程与线程的区别

  • 同一进程内的多个线程共享该进程内的地址资源
  • 创建线程的开销要远小于创建进程的开销(创建一个进程,就是创建一个车间,涉及到申请空间,而且在该空间内建至少一条流水线,但创建线程,就只是在一个车间内造一条流水线,无需申请空间,所以创建开销小)

3.多线程应用举例

开启一个字处理软件进程,该进程肯定需要办不止一件事情,比如监听键盘输入,处理文字,定时自动将文字保存到硬盘,这三个任务操作的都是同一块数据,因而不能用多进程。只能在一个进程里并发地开启三个线程,如果是单线程,那就只能是,键盘输入时,不能处理文字和自动保存,自动保存时又不能输入和处理文字。

二、开启线程的两种方式

方式一:

import time, random
from threading import Thread

def task(name):
    print('%s is running' %name)
    time.sleep(random.randrange(1, 3))
    print('%s running end' %name)

if __name__ == '__main__':
    t1 = Thread(target=task, args=('gudon', ))
    t1.start()
    print('主线程。。。')

---------------------------打印结果-----------------------------
gudon is running
主线程。。。
gudon running end

方式二:

import time, random
from threading import Thread

class MyThread(Thread):
    def __init__(self, name):
        super().__init__()
        self.name = name

    def run(self):
        print('%s is running' %self.name)
        time.sleep(random.randrange(1,3))
        print('%s running end' %self.name)

if __name__ == "__main__":
    t1 = MyThread('Astro')
    t1.start()
    print('主线程......')

---------------------------打印结果-----------------------------
Astro is running
主线程......
Astro running end

multprocess 、threading 两个模块在使用方式上相似性很大

二、进程与线程的区别

1.开线程的开销远小于开进程的开销

进程:

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

def task(name):
    print('%s is running' %name)
    time.sleep(random.randrange(1, 3))
    print('%s running end' %name)

if __name__ == '__main__':
    p = Process(target=task, args=('Astro', ))
    p.start() # p.start ()将开启进程的信号发给操作系统后,操作系统要申请内存空间,让好拷贝父进程地址空间到子进程,开销远大于线程
    print('主...........')

---------------------------【进程】打印结果-----------------------------
主...........
Astro is running
Astro running end

线程:

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

def task(name):
    print('%s is running' %name)
    time.sleep(random.randrange(1, 3))
    print('%s running end' %name)

if __name__ == '__main__':
    t1 = Thread(target=task, args=('Astro', ))
    t1.start() #几乎是t.start ()的同时就将线程开启了,线程的创建开销要小鱼进程创建的开销
    print('主...........')

---------------------------【线程】打印结果-----------------------------

Astro is running
主...........
Astro running end

2.同一进程内的多个线程共享该进程的地址空间

from threading import Thread
from multiprocessing import Process

n = 100
def task():
    global n
    n = 0

if __name__ == "__main__":
    p1 = Process(target=task)
    p1.start()
    p1.join()

    print('主 %s' %n)  

---------------------------打印结果-----------------------------
主 100

子进程 p1 创建的时候,会把主进程中的数据复制一份,进程之间数据不会互相影响,所以子进程p1 中 n=0 后,只是子进程中的 n 改了,主进程的 n 不会受影响 ,即 进程之间地址空间是隔离的

from threading import Thread
from multiprocessing import Process

n = 100
def task():
    global n
    n = 0

if __name__ == "__main__":
    t1 = Thread(target=task)
    t1.start()
    t1.join()

    print('主 %s' %n)

---------------------------打印结果-----------------------------
主 0

同一进程内的线程之间共享进程内的数据,所以为 0

3. pid

pid 就是 process id ,进程的id号。

开多个进程,每个进程都有不同的pid

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

def task():
   # print('子进程...', current_process().pid)  # 也可以使用 os.getpid()或 current_process().pid 来查看当前进程的pid,os.getppid() 可以查看当前进程的父进程的pid
    print('子进程PID:%s  父进程的PID:%s' % (os.getpid(), os.getppid()))

if __name__ == '__main__':
    p1 = Process(target=task)
    p1.start()

    print('主线程', current_process().pid)

---------------------------打印结果-----------------------------
主线程 808
子进程PID:7668  父进程的PID:808

在主进程下开启多个线程,每个线程都跟主进程的pid一样

from threading import Thread
import os
def task():
    print('子线程pid:',os.getpid())

if __name__ == '__main__':
    t1 = Thread(target=task, )
    t1.start()

    print('主线程pid:', os.getpid())

---------------------------打印结果-----------------------------
子线程pid: 9084
主线程pid: 9084

三、Thread对象的其他属性或方法

Thread实例对象的方法
  # isAlive(): 返回线程是否活动的。
  # getName(): 返回线程名。
  # setName(): 设置线程名。

threading模块提供的一些方法:
  # threading.currentThread(): 返回当前的线程变量。
  # threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。
  # threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。
from threading import Thread, currentThread,active_count,enumerate
import time
def task():
    print('%s is running '%currentThread().getName())
    time.sleep(2)
    print('%s is done' %currentThread().getName())

if __name__ == "__main__":
    t = Thread(target=task, )
    # t = Thread(target=task, name='子线程001')  # 也可以在这里改子线程名字
    t.start()
    # t.setName('子线程001')
    t.join()
    currentThread().setName('主线程')
    print(active_count())  # 返回正在运行的线程数量
    print(enumerate())  # [<_MainThread(主线程, started 6904)>]     默认为:[<_MainThread(MainThread, started 6904)>]

---------------------------打印结果-----------------------------
子线程001 is running
子线程001 is done
1
[<_MainThread(主线程, started 6432)>]

四、守护线程与互斥锁

1.守护线程

无论是进程还是线程,都遵循:守护xxx会等待主xxx运行完毕后被销毁

1、对主进程来说,运行完毕指的是主进程代码运行完毕

主进程在其代码结束后就已经算运行完毕了(守护进程在此时就被回收),然后主进程会一直等非守护的子进程都运行完毕后回收子进程的资源(否则会产生僵尸进程),才会结束,

2、对主线程来说,运行完毕指的是主线程所在的进程内所有非守护线程统统运行完毕,主线程才算运行完毕

主线程在其他非守护线程运行完毕后才算运行完毕(守护线程在此时就被回收)。因为主线程的结束意味着进程的结束,进程整体的资源都将被回收,而进程必须保证非守护线程都运行完毕后才能结束。
from threading import Thread
import time

def sayHi(name):
    time.sleep(2)
    print('%s say Hello')

if __name__ == '__main__':
    t = Thread(target=sayHi, args=('Astro', ))
    t.setDaemon(True)  # 设置为守护线程, t.daemon = True 也可以
    t.start()

    print('主线程')
    print(t.is_alive())

----------------------执行结果---------------------------
主线程
True

t.start() 后,系统会马上创建出一条子线程,但是由于子线程中 time.sleep(2) ,2秒的时间对计算机来说已经很长了,所以在

print('主线程')
print(t.is_alive())

执行后,主线程结束,子线程跟着就结束了,因为我们设置了该子线程为守护线程

在看一个例子:

def foo():
    print('foo runing..')
    time.sleep(1)
    print('foo end')

def bar():
    print('bar running..')
    time.sleep(3)
    print('bar end')

if __name__ == '__main__':
    t1 = Thread(target=foo, )
    t2 = Thread(target=bar, )

    t1.setDaemon(True)
    t1.start()
    t2.start()

    print('main.......')

----------------------执行结果---------------------------
foo runing..
bar running..
main.......
foo end
bar end

t1 为守护进程,t1.start() 后,马上执行 foo函数,然后 time.sleep(1)

此时 t2.start() 开启了线程,执行 bar函数后 time.sleep(3)

3秒的时间内,执行了主线程的 print(‘main......‘) , 然后主线程任务已经完成,但是由于 子线程t2 还未执行完毕,t2 非守护线程,主线程还需要等待 非守护线程t2运行完毕后才能结束,所以在等待 t2结束的时间里,t1 线程执行完毕,t1只sleep 1秒,时间上足够t1 先执行了

然后t2 三秒后执行自己剩下的部分

2.互斥锁

from threading import Thread, Lock
import time

n = 100
def task(mutex):
    global n
    mutex.acquire()  # 加互斥锁
    temp = n
    time.sleep(0.1)
    n = temp - 1
    mutex.release()

if __name__ == '__main__':
    mutex = Lock()
    t_list = []
    for i in range(100):
        t = Thread(target=task, args=(mutex, ))
        t_list.append(t)
        t.start()

    for t in t_list:
        t.join()

    print('main....',n)

----------------------执行结果---------------------------
main.... 0

如果不加互斥锁的情况下,得到的结果是 main.... 99

因为,在循环中 t.start() 的时候,100个线程会立马创建出来,然后在函数中,100个线程的 temp都被赋值为了 100,所以 n = temp - 1 只是循环的被赋值为 99 而已

另外,互斥锁只保证里局部的串行,与join 不一样

五、GIL 全局解释器锁

运行这段代码

import os, time
print(os.getpid())  # 5132
time.sleep(1000)

然后cmd 查看一下 tasklist |findstr python

结果:

python.exe                    5132 Console                    1      9,568 K

原文地址:https://www.cnblogs.com/friday69/p/9615654.html

时间: 2024-10-28 02:32:43

python 并发编程之多线程的相关文章

Python并发编程04/多线程

目录 Python并发编程04/多线程 1.生产消费者模型 2.线程的理论知识 2.1什么是线程 2.2线程vs进程 2.3线程的应用 3.开启进程的两种方式 3.1第一种方式 3.2第一种方式 4.线程vs进程的代码对比 4.1开启速度对比 4.2对比pid 4.3同一个进程内线程共享内部数据 5.线程的其他方法 6.join与守护线程 6.1join 6.2守护线程 7.互斥锁 Python并发编程04/多线程 1.生产消费者模型 #编程思想,模型,设计模式,理论等等,都是交给你一种编程的方

python并发编程之多线程理论部分

阅读目录 一 什么是线程 二 线程的创建开销小 三 线程与进程的区别 四 为何要用多线程 五 多线程的应用举例 六 经典的线程模型(了解) 七 POSIX线程(了解) 八 在用户空间实现的线程(了解) 九 在内核空间实现的线程(了解) 十 用户级与内核级线程的对比(了解) 十一 混合实现(了解) 一 什么是线程 在传统操作系统中,每个进程有一个地址空间,而且默认就有一个控制线程 线程顾名思义,就是一条流水线工作的过程,一条流水线必须属于一个车间,一个车间的工作过程是一个进程 车间负责把资源整合到

python并发编程之多线程2------------死锁与递归锁,信号量等

一.死锁现象与递归锁 进程也是有死锁的 所谓死锁: 是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用, 它们都将无法推进下去.此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程, 如下就是死锁 1 死锁------------------- 2 from threading import Thread,Lock,RLock 3 import time 4 mutexA = Lock() 5 mutexB = Lock() 6

python并发编程之多线程

一,什么是线程 线程也被称为轻量进程计算机科学术语,指运行中的程序的调度单位. 线程是进程中的实体,一个进程可以拥有多个线程,一个线程必须有一个父进程.线程不拥有系统资源,只有运行必须的一些数据结构:它与父进程的其它线程共享该进程所拥有的全部资源.线程可以创建和撤消线程,从而实现程序的并发执行.一般,线程具有就绪.阻塞和运行三种基本状态. 二,线程与进程的区别 1) 简而言之,一个程序至少有一个进程,一个进程至少有一个线程. 2) 线程的划分尺度小于进程,使得多线程程序的并发性高. 3) 另外,

python并发编程之多线程基础知识点

1.线程理论知识 概念:指的是一条流水线的工作过程的总称,是一个抽象的概念,是CPU基本执行单位. 进程和线程之间的区别: 1. 进程仅仅是一个资源单位,其中包含程序运行所需的资源,而线程就相当于车间的流水线,负责执行具代码. 2. 每个进程至少包含一个线程,由操作系统自动创建,称之为主线程 3. 每个进程可以有任意数量的线程 4.创建进程的开销要比创建进程小得多 5. 同一进程的线程间数据是共享的 6.线程之间是平等的,没有子父级关系,同一进程下的各线程的PID相同 7. 创建线程的代码可以写

python并发编程之多线程(实践篇)

一.threading模块介绍 官网链接:https://docs.python.org/3/library/threading.html?highlight=threading# 1.开启线程的两种方式 #直接调用 import threading import time def run(n): print('task',n) time.sleep(2) t1 = threading.Thread(target=run,args=('t1',)) t1.start() #继承式调用 mport

python并发编程:多线程-线程理论

一 什么是线程 在传统操作系统中,每个进程有一个地址空间,而且默认就有一个控制线程 线程顾名思义,就是一条流水线工作的过程(流水线的工作需要电源,电源就相当于CPU),而一条流水线必须属于一个车间,一个车间的工作过程是一个进程,车间负责把资源整合到一起,是一个资源单位,而一个车间内至少有一条流水线. 所以,进程只是用来把资源集中到一起(进程只是一个资源单位,或者说资源集合),而线程才是CPU上的执行单位. 多新村(即多个控制线程)的概念时:在一个进程中存在多个线程,多个线程共享该进程的地址空间,

python并发编程:多线程-开启线程的两种方式

一 threading模块介绍 multiprocess模块完全模仿了threading模块的接口,二者在使用层面,有很大的相似性 二 开启线程的两种方式 方式一 from threading import Thread import time def sayhi(name): time.sleep(2) print("%s say hello" % name) if __name__ == '__main__': t = Thread(target=sayhi, args=('mik

python并发编程之多线程编程

一.threading模块介绍 multiprocess模块的完全模仿了threading模块的接口,二者在使用层面,有很大的相似性,因而不再详细介绍 二.开启线程的两种方式 方式一: from threading import Thread import time import random def task(name): print('%s is running' %name) time.sleep(random.randint(1,3)) print('%s is end' %name)