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

1、线程理论知识

  概念:指的是一条流水线的工作过程的总称,是一个抽象的概念,是CPU基本执行单位。

  进程和线程之间的区别:

    1. 进程仅仅是一个资源单位,其中包含程序运行所需的资源,而线程就相当于车间的流水线,负责执行具代码。

    2. 每个进程至少包含一个线程,由操作系统自动创建,称之为主线程

    3. 每个进程可以有任意数量的线程

    4.创建进程的开销要比创建进程小得多

    5. 同一进程的线程间数据是共享的

    6.线程之间是平等的,没有子父级关系,同一进程下的各线程的PID相同

    7. 创建线程的代码可以写在任意位置,不一定非要在main函数下。

  为什么使用线程:

    提高程序执行效率

2、开启线程的两种方式

  和进程类似,但是开启方式不一定非要建在main函数下。

# 第一种方式,实例化 Thread
# from threading import Thread
#
# def task():
#     print("subthread is running....")
#
# t = Thread(target=task)
# t.start()
# print(‘main is over....‘)

# 第二种方式,继承Thread类

from threading import Thread

class MyThread(Thread):
    def run(self):
        print("subthread is running....")

两种方式

3、主线程和子线程之间的关系

  1. 主线程任务执行完毕后,主线程会等待所有子线程全部执行完毕后结束

  2. 在同一进程中,所有线程都是平等的,没有子父级关系

# 验证主线程代码执行完后会不会立即结束,
import random
import time
import threading
from threading import Thread
def task(name):
    print("%s is running..." % name)
    time.sleep(random.randint(1, 3))
    print(threading.enumerate())
    print("%s is over....." % name)

t = Thread(target=task, args=(‘aaa‘,))
t.start()

print(‘main over....‘)

# 验证主线程代码执行完后会不会立即结束,

4、验证线程和进程之间的区别

from threading import Thread
import time

def task():
    global num
    time.sleep(1)
    num -= 1
num = 10
t = Thread(target=task,)
t.start()
t.join()
print(num)

同一进程中线程的数据是可以共享的

from multiprocessing import Process
from threading import Thread
import time

def task():
    pass

def expense(cls):
    """用来测试线程或进程创建开销"""
    lis = []
    start = time.time()
    for i in range(50):
        p = cls(target=task,)
        p.start()
        lis.append(p)
    for p in lis:
        p.join()
    return time.time()-start

创建线程的开销要比创建进程小的多

5、线程的安全问题

 1.互斥锁

  数据共享必然会造成竞争,竞争就会造成数据错乱问题。

  解决办法:和进程一样,加互斥锁。

from threading import Thread, Lock
import time

num = 10

def task(lock):
    global num
    lock.acquire()
    a = num
    time.sleep(0.5)
    num = a-1
    lock.release()

ts = []
lock = Lock()
for i in range(10):
    t = Thread(target=task,args=(lock,))
    t.start()
    ts.append(t)
for t in ts:
    t.join()
print(num)

加互斥锁,保证数据安全

  2.死锁

  死锁不是一种锁,而是一种锁的状态,

  一般出现死锁的情况有两种:

    1. 对同一把锁多次acquire.(使用RLOCK锁,代替LOCK)

    2. 两个或两个以上的进程或线程在执行过程中,因争夺资源造成的相互等待现象。(解决办法:能不加最好不加,要加就只加一把)

from threading import Thread, Lock
import time

def task1(name, locka, lockb):
    locka.acquire()
    print("%s拿到a锁"%name)
    time.sleep(0.3)
    lockb.acquire()
    print(‘%s拿到b锁‘%name)
    lockb.release()
    locka.release()
def task2(name, locka, lockb):
    lockb.acquire()
    print("%s拿到b锁"%name)
    time.sleep(0.3)
    locka.acquire()
    print(‘%s拿到a锁‘%name)
    locka.release()
    lockb.release()

locka = Lock()
lockb = Lock()
t1 = Thread(target=task1, args=(‘t1‘, locka, lockb))
t2 = Thread(target=task2, args=(‘t2‘, locka, lockb))
t1.start()
t2.start()

死锁的第二种情况的示例

  3.可重入锁

  只能解决同一线程多次执行acquire情况。

  只有一个线程所有的acquire都被释放,其他线程才能拿到这个锁。

  也会发生死锁现象。

from threading import Thread, RLock

lock = RLock()
lock.acquire()
lock.acquire()
lock.acquire()
lock.acquire()

print("over")
lock = RLock()

def task1():
    lock.acquire()
    print(‘task1‘)
def task2():
    lock.acquire()
    print(‘task2‘)

Thread(target=task1).start()
Thread(target=task2).start()

示例

  4. 信号量

  也是一种锁,用来控制同一时间,有多少线程可以提供并发访问,不是用来处理线程安全问题

from threading import Semaphore, Thread
import time
s_lock = Semaphore(3)

def task():
    s_lock.acquire()
    time.sleep(1)
    print("run.....")
    s_lock.release()

for i in range(20):
    t = Thread(target=task)
    t.start()

示例

6、守护线程

  守护线程在所有非守护线程结束后结束。

import threading
from threading import Thread
import time

def task1():
    print(‘thread-1 is running...‘)
    time.sleep(3)
    print(‘thread-1 over....‘)

def task2():
    print(‘thread-2 is running...‘)
    time.sleep(1)
    print(‘thread-2 over....‘)

if __name__ == ‘__main__‘:
    t1 = Thread(target=task1,)
    t2 = Thread(target=task2,)
    t1.setDaemon(True)
    t1.start()
    t2.start()
    print(t1.ident)
    print(threading.enumerate())
    print("main over...")

示例

7、GIL

  全局解释器锁,是一互斥锁,只有在Cpython解释器存在。

  为什么需要:因为一个python.exe进行运行只有一份解释器,如果这个进程开启的多个线程都要执行代码,多线程之间就要竞争解释器,一旦竞争就有可能出现问题。

  带来的好处:保证了多线程同时访问解释器时的数据安全问题。

  带来的问题:同一时间只有一个线程访问解释器,使得多线程无法真正的并发

  出现的原因:默认情况下,一个进程只有一个线程不会是不会出问题,但不要忘了还有GC线程,一旦出现多个线程就可能出现问题,所以当初就简单粗暴的加上了GIL锁

  GIL加锁和解锁时机:

    加锁:在调用解释器时立即加锁

    解锁:当前线程遇到IO时释放,或者当前线程执行超过设定值释放(py2计算的是执行代码的行数,py3中计算的是时间)

  解决办法:使用多进程或使用其他的python解释器

8、线程池和进程池

  一种容器,本质十一存储线程或进程的列表

  为什么使用? 因为服务器不能无限开启线程或进程,所以需要对线程数量加以控制,线程池就是帮我们完成线程/进程的创建、销毁以及任务分配

  特点:

    线程池在创建时不会开启线程,

    等到任务提交时,如果没有空闲线程,并且已存在的线程数量小于最大值,开启新线程,

    线程开启后不会关闭,直到进程全部结束为止

from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor
pool= ProcessPoolExecutor(maxsize),创建进程池,maxsize为最大进程个数

res = pool.submit(task, ‘a‘), 提交任务

res.result(timeout),接收调用的返回值,timeout为超时时间,超时报错
该函数是阻塞函数,会一直等待任务执行完毕
pool.shutdown(wait),所有任务执行完毕,阻塞函数
wait=True, 等待池内所有任务执行完毕后回收资源才继续
wait=False,立即返回,并不会等待池内的任务执行完毕

方法和属性

from concurrent.futures import ThreadPoolExecutor
import time

def task(num):
    time.sleep(0.5)
    print("%s is running....."%num)
    return num**2

pool = ThreadPoolExecutor()
ress = []
for i in range(10):
    res = pool.submit(task, i)
    ress.append(res)

pool.shutdown(wait=False)

for i in ress:
     print(i.result())

print(‘over‘)

示例

9、同步异步阻塞非阻塞

  阻塞和非阻塞都是指程序的运行状态

    阻塞:当程序执行遇到IO操作,无法继续执行代码

    非阻塞:程序执行没有遇到IO操作,或通过某种方式,使程序遇到了也不会停在原地,还可以继续执行

  同步异步指的是提交任务的方式

    同步:发起任务后必须原地等待任务执行完成,才可以继续执行

    异步:发起任务后不用等待任务执行,可以立即执行其他操作

    异步效率高于同步,发起异步任务方式:就是多线程和多进程

  同步和阻塞的不同:阻塞一定使CPU已经切换,同步虽然在等待,但CPU没有切走,还在当前进程中执行其他任务

    

原文地址:https://www.cnblogs.com/ywsun/p/10493770.html

时间: 2024-10-11 07:26: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上的执行单位. 2.进程与线程的区别 同一进程内的多个线程共享该进程内的地址资源 创建线程的开销要远小于创建进程的开销(创建一个进程,就是创建一个车间,涉及到申请空间,而且在该空间内建至少一条流水线,但创建线程,就只是在一个车间内造一

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

并发编程之多线程基础-Thread和Runnable的区别及联系(二)

上篇文章讲述了创建线程的常用方式 本篇主要分析一下Thread和Runnable两种方式创建线程的区别及联系 联系: ?Thread类实现了Runable接口. ?都需要重写里面Run方法. 区别: ?Thread方式不支持多继承,Runnable方式支持多个实现 ?Runnable更容易实现资源共享,能多个线程同时处理一个资源. 疑惑分享: 本人在理解他们区别的时候 考虑到Thread类本身就是Runnable的实现类 所以产生了一个疑惑:- 为什么Runnable可以实现共享而Thread却