并发编程 - 线程

并发编程 - 线程

1、什么是线程

  进程:资源单位

  线程:执行单位

 

  线程与进程都是虚拟的概念,只是为了更好地表达某种事物

 

  注意:开启一个进程,一定会自带一个线程,线程才是真正的执行者

 

 

2、为什么要使用线程

  > 为了节省资源的占用

  > 多线程执行速度非常快 

 

  开启进程会发生什么:

    > 启动进程会产生一个内存空间,申请一块资源

    > 会自带一个主线程

    > 开启子进程的速度要比开启子线程的速度慢

  开启线程会发生什么:

    > 一个进程内可以开启多个线程,从进程的内存空间中申请资源

    > 节省资源

  # 比如开启三个进程:

    # 占用三分内存资源

  # 比如开启三个线程:

    # 从一个内存资源中,申请三个小的执行单位

 

  问题1:在单核情况下,开启多进程有没有提高执行效率?

    没有,在单核情况下,尽量开启多线程。

 

  问题:IO密集型、计算密集型分别使用什么?

    IO密集型:多线程

      IO(时间由用户决定):

        阻塞:切换+保存状态

    计算密集型:多进程

      计算(时间由操作系统定):

        计算时间很长:切换+保存状态

 

  注意:进程与进程之间数据是隔离的,线程与线程之间的数据是共享的

 

 

3、线程的两种创建方式、守护线程

  守护线程的使用方式和守护进程的使用方式一样,在下面创建线程中体现:

 

  第一种方式:直接调用Thread类

from threading import Thread    # Thread用来创建线程
from threading import current_thread    # current_thread中含有线程的属性
import time


def task():
    print(f"start...{current_thread().name}")    # current_thread().name 线程的名字
    time.sleep(1)
    print(f"end...{current_thread().name}")    # current_thread().name 线程的名字


if __name__ == '__main__':
    # 模拟开启10个子线程
    for i in range(10):
        # 开启子线程
        t = Thread(target=task)
        # 加上守护线程:主进程结束,代表主线程也结束,子线程有可能会被回收,将其他线程回收资源
        t.daemon = True
        t.start()
        # 告诉主线程等到子线程执行完成再执行主线程
        t.join()

    print(f"主进程(主线程)结束...{current_thread().name}")    # current_thread().name 线程的名字

  执行结果:

start...Thread-1
end...Thread-1
start...Thread-2
end...Thread-2
start...Thread-3
end...Thread-3
start...Thread-4
end...Thread-4
start...Thread-5
end...Thread-5
start...Thread-6
end...Thread-6
start...Thread-7
end...Thread-7
start...Thread-8
end...Thread-8
start...Thread-9
end...Thread-9
start...Thread-10
end...Thread-10
主进程(主线程)结束...MainThread

 

  第二种方式:继承Thread类

from threading import Thread    # Thread用来创建线程
from threading import current_thread    # current_thread中含有线程的属性
import time


class MyThread(Thread):

    def run(self):
        print(f"start...{current_thread().name}")  # current_thread().name 线程的名字
        time.sleep(1)
        print(f"end...{current_thread().name}")  # current_thread().name 线程的名字


if __name__ == '__main__':
    # 开启子线程
    t = MyThread()
    # 加上守护线程:主进程结束,代表主线程也结束,子线程有可能会被回收,将其他线程回收资源
    t.daemon = True
    t.start()
    # 告诉主线程等到子线程执行完成再执行主线程
    t.join()

    print(f"主进程(主线程)结束...{current_thread().name}")    # current_thread().name 线程的名字

  执行结果:

start...Thread-1
end...Thread-1
主进程(主线程)结束...MainThread

 

 

4、线程间数据是互通的

from threading import Thread
import time


number = 100
def task():
    global number
    number += 100
    print("start...")
    time.sleep(1)
    print("end...")


if __name__ == '__main__':
    # 开启子线程
    t = Thread(target=task)
    t.start()
    # 告诉主线程等到子线程执行完成再执行主线程
    t.join()

    print("主进程(主线程)开始...")
    print(number)print("主进程(主线程)结束...")

  执行结果:

start...
end...
主进程(主线程)开始...
200
主进程(主线程)结束...

 

 

5、多线程互斥锁

  多线程互斥锁是一把锁,将并发改为串行,牺牲了效率,保证了数据读写正确

  作用:

    某个线程要更改共享数据时,先将其锁定require,此时资源的状态为“锁定”,其他线程不能更改;直到该线程释放资源release,将资源的状态变成“非锁定”,其他的线程才能再次锁定该资源。线程互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性。

 

  多线程实现并发会造成数据不正确,可以引入互斥锁来避免这种问题(PS:在不使用互斥锁的情况下,假如有10个线程要对一个数据进行修改 -1,正确的结果应该是 -10,但是这10个线程并发同时去对一个数据进行修改,修改的是同一份数据,结果却是 -1,这个时候就出现了数据的不正确;在使用互斥锁的情况下,会将10个线程由并发改为串行,一个一个去对数据进行修改,这样保证每个线程都会对数据修改成功,保证了数据的正确性)

 

  例:开启十个线程,对一个数据进行修改(将十个线程由并发改为串行,一个一个去对数据进行修改)

from threading import Thread
from threading import Lock
import time


number = 100

def task(lock):
    global number
    # 加锁
    lock.acquire()
    number2 = number
    time.sleep(0.5)
    number = number2 - 1
    # 释放锁
    lock.release()


if __name__ == '__main__':
    lock = Lock()
    list1 = []
    for i in range(10):
        t = Thread(target=task, args=(lock, ))
        t.daemon = True
        t.start()
        list1.append(t)

    for t in list1:
        t.join()

    print(number)

  执行结果:

90

 

 

6、线程池

  作用:用来限制线程的数量,保证了硬件跟得上软件的发展

 

from concurrent.futures import ThreadPoolExecutor

# pool限制一次只能创建10个线程,如果要创建50个线程,就要分成5次创建
pool = ThreadPoolExecutor(10)
def task(i): print(i) if __name__ == '__main__': # 开启50个线程 for i in range(50):
     # submit(函数名, 参数)方法实现并发 pool.submit(task,
123)

  执行结果:

123
123
123
123
123
123
123
123
123
123
123123

123
123
123
123
123
123
123123
123
123
123123
123
123
123
123


123
123
123
123
123
123
123123123
123
123
123
123
123
123
123

123
123
123
123
123
123

 

 

7、线程池中的回调函数callback

from concurrent.futures import ThreadPoolExecutor


# 并发执行的任务
def task1():
    return "壹贰弎肆伍陆柒捌玖拾"


# 自定义回调函数
def task2(obj):    # 这里的obj参数就是task1返回的值
    # 查看obj的名称空间
    print(obj.__dict__)
    # 使用obj对象中的result方法可以取出task1返回的结果 "壹贰弎肆伍陆柒捌玖拾"
    result = obj.result()    # "壹贰弎肆伍陆柒捌玖拾"
    # 使用_result也可以取出task1返回的结果 "壹贰弎肆伍陆柒捌玖拾"
    # result = obj._result    # "壹贰弎肆伍陆柒捌玖拾"
    print(result)


if __name__ == '__main__':
    pool = ThreadPoolExecutor(5)
    for i in range(5):
        # submit(函数名, 函数接收的参数1, 参数2...)
        # add_done_callback(回调函数的名字),会将submit提交的task执行的结果,传给task2中的第一个参数,第一个参数是一个对象。
        pool.submit(task1).add_done_callback(task2)

  执行结果:

{'_condition': <Condition(<unlocked _thread.RLock object owner=0 count=0 at 0x00000000003A9A20>, 0)>, '_state': 'FINISHED', '_result': '壹贰弎肆伍陆柒捌玖拾', '_exception': None, '_waiters': [], '_done_callbacks': []}
壹贰弎肆伍陆柒捌玖拾
{'_condition': <Condition(<unlocked _thread.RLock object owner=0 count=0 at 0x00000000003A9A20>, 0)>, '_state': 'FINISHED', '_result': '壹贰弎肆伍陆柒捌玖拾', '_exception': None, '_waiters': [], '_done_callbacks': []}
壹贰弎肆伍陆柒捌玖拾
{'_condition': <Condition(<unlocked _thread.RLock object owner=0 count=0 at 0x00000000003A9A20>, 0)>, '_state': 'FINISHED', '_result': '壹贰弎肆伍陆柒捌玖拾', '_exception': None, '_waiters': [], '_done_callbacks': []}
壹贰弎肆伍陆柒捌玖拾
{'_condition': <Condition(<unlocked _thread.RLock object owner=0 count=0 at 0x00000000003A9A20>, 0)>, '_state': 'FINISHED', '_result': '壹贰弎肆伍陆柒捌玖拾', '_exception': None, '_waiters': [], '_done_callbacks': []}
壹贰弎肆伍陆柒捌玖拾
{'_condition': <Condition(<unlocked _thread.RLock object owner=0 count=0 at 0x00000000003A9A20>, 0)>, '_state': 'FINISHED', '_result': '壹贰弎肆伍陆柒捌玖拾', '_exception': None, '_waiters': [], '_done_callbacks': []}
壹贰弎肆伍陆柒捌玖拾

 

  PS:进程池中的回调函数和线程池中的回调函数使用方式一样

posted @ 2020-11-07 16:58  chchcharlie、  阅读(172)  评论(0编辑  收藏  举报