并发编程 线程

线程

在传统操作系统中,每个进程有一个地址空间,而且默认就有一个控制线程

线程顾名思义,就是一条流水线工作的过程,一条流水线必须属于一个车间,一个车间的工作过程是一个进程

车间负责把资源整合到一起,是一个资源单位,而一个车间内至少有一个流水线

流水线的工作需要电源,电源就相当于cpu

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

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

例如,北京地铁与上海地铁是不同的进程,而北京地铁里的13号线是一个线程,北京地铁所有的线路共享北京地铁所有的资源,比如所有的乘客可以被所有线路拉。

开启线程的两种方式

复制代码
from threading import Thread
import time,os

# 第一种方式
def task():
    print('%s is running' %os.getpid())
    time.sleep(2)
    print('%s is done' %os.getpid())

if __name__ == '__main__':
    t = Thread(target=task,)
    t.start()
    print('主')
    '''
    1.一个子进程内不开进程也不开子线程:主线程结束,该进程就结束
    2.当一个进程内开启子进程时:
        主线程结束,主进程要等,等所有子进程运行完毕给儿子收尸
    3.当一个进程内开启多个线程时:
        主线程结束并不意味着进程结束,
        进程的结束指的是该进程内所有的线程都运行完毕,才应该回收进程
    '''
# 第二种方式
class Mythread(Thread):
    def __init__(self):
        super().__init__()
    def run(self):
        print('%s is running' % os.getpid())
        time.sleep(2)
        print('%s is done' % os.getpid())

if __name__ == '__main__':
    t = Mythread()
    t.start()
    print('主')
复制代码

多线程指的是,在一个进程中开启多个线程,简单的讲:如果多个任务共用一块地址空间,那么必须在一个进程内开启多个线程。详细的讲分为4点:

  1. 多线程共享一个进程的地址空间

      2. 线程比进程更轻量级,线程比进程更容易创建可撤销,在许多操作系统中,创建一个线程比创建一个进程要快10-100倍,在有大量线程需要动态和快速修改时,这一特性很有用

      3. 若多个线程都是cpu密集型的,那么并不能获得性能上的增强,但是如果存在大量的计算和大量的I/O处理,拥有多个线程允许这些活动彼此重叠运行,从而会加快程序执行的速度。

      4. 在多cpu系统中,为了最大限度的利用多核,可以开启多个线程,比开进程开销要小的多。(这一条并不适用于python)

线程对象的其它属性和方法

复制代码
from threading import Thread,current_thread,enumerate,active_count
import time,os

def task():
    print('%s is running' %current_thread().getName())  # 线程名 current_thread()当前线程
    time.sleep(2)
    print('%s is done' %os.getpid())

if __name__ == '__main__':
    # t = Thread(target=task,name='xxx')
    t = Thread(target=task)
    t.start()
    # t.join() 
    # print(t.name)  # 线程名 Thread-1
    print(enumerate())  # 当前或者的线程对象
    print(active_count())  # 当前活着线程的线程数
    print('主',current_thread().getName())  # MainThread
复制代码

线程与进程内存空间占用

复制代码
# 进程之间内存空间隔离
from multiprocessing import Process

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

if __name__ == '__main__':
    t = Process(target=task,)
    t.start()
    t.join()
    print('主',n)  # 100

# 线程之间内存空间共享
from threading import Thread

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

if __name__ == '__main__':
    t = Thread(target=task,)
    t.start()
    t.join()
    print('主',n)  # 0
复制代码

线程池

复制代码
from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor
from threading import current_thread
import time,random,os
def task(n):
    print('%s is running'%current_thread().getName())
    time.sleep(random.randint(1,3))
    return n**2

if __name__ == '__main__':
    # t = ProcessPoolExecutor()  # 默认是cpu的核数
    # print(os.cpu_count())  # 查看cpu核数
    t = ThreadPoolExecutor(3)  # 默认为cpu的核数*5
    objs = []
    for i in range(10):
        obj = t.submit(task,i)
        objs.append(obj)
    t.shutdown(wait=True)
    for obj in objs:
        print(obj.result())
    print('主',current_thread().getName())
复制代码

异步调用和回调函数

复制代码
import requests
from threading import Thread,current_thread
from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor
import time

def get(url):
    print('%s GET %s'%(current_thread().getName(),url))
    response = requests.get(url)
    if response.status_code == 200:
        return {'url':url,'text':response.text}
    # print(type(response.text))  # <class 'str'>

def parse(obj):
    res = obj.result()
    print('[%s] <%s> (%s)' % (current_thread().getName(), res['url'],len(res['text'])))
    # print('[%s] parse res [%s]'%(res['url'],len(res['text'])))

if __name__ == '__main__':
    urls = [
        'https://www.python.org',
        'https://www.baidu.com',
        'https://www.jd.com'
    ]
    t = ThreadPoolExecutor(2)
    # t = ProcessPoolExecutor(2)
    for url in urls:
        t.submit(get,url).add_done_callback(parse)  # parse(obj)
    t.shutdown(wait=True)
    print('主')

'''
异步调用:
    提交完任务(为该任务绑定一个回调函数),不用在原地等任务执行完毕拿到结果,可以直接提交下一个任务
    一个任务一旦执行完毕就会自动触发回调函数的运行
回调函数的参数是单一的:
    回调函数的参数就是它所绑定任务的返回值
'''
# 进程池,回调的活由主进程干
# 线程池,回调的活都有可能干
复制代码
原文地址:https://www.cnblogs.com/QQ279366/p/7954325.html