多线程

作者: 程序员Darker | 来源:发表于2019-02-27 15:14 被阅读0次

线程

概述

通俗点讲,在共享的空间里同时做不同的任务

进程和线程的区别

  1. 同一个进程中的所有线程共享同一内存空间,但是2个进程之间内存空间是独立的。
  2. 同一个进程中的所有线程的数据是共享的,多个进程之间的数据是独立的。
  3. 进程安全性高,线程安全性低
  4. 进程是资源分配的单元,线程是调度单元

python多线程编程

多线程修改全局变量

import time
from threading import Thread

"""多线程实现修改全局变量"""
# 定义一个全局变量
num = 0
print("num=", num)


def modify1():
    global num
    num += 1
    print('修改一次num')


def modify2():
    global num
    num += 1
    print('修改一次num')


def read():
    print("读出线程,num的值是", num)


# 创建一个线程
t1 = Thread(target=modify1)
t2 = Thread(target=modify2)
t3 = Thread(target=read)
# 启动线程
t1.start()
t2.start()
t3.start()
# 睡眠一秒,防止线程未执行完就输出num
time.sleep(1)
print("主线程,num的值是", num)

类继承

from threading import Thread

"""简单多线程类继承"""


# 创建一个自己的多线程继承类
class MyThread(Thread):
    def __init__(self, name):
        # 调用父类的构造方法
        Thread.__init__(self)
        # super(MyThread, self).__init__()
        self.name = name

    def run(self):
        print("类继承方式,我是", self.name)


# 实体化出来一个对象
myt1 = MyThread("江小白")
# 启动线程,线程就去执行类里的run方法
myt1.start()

线程的同步和互斥

临界资源

多线程编程时主要考虑内容:全局变量和代码资源

全局变量访问临界区

from threading import Thread

global_variable = 0


# 线程1,写数据
def write_data1():
    global global_variable
    for x in range(1000000):
        global_variable += 1
    print("线程1看到的global_variable:", global_variable)


# 线程2,写数据
def write_data2():
    global global_variable
    for x in range(1000000):
        global_variable += 1
    print("线程2看到的global_variable:", global_variable)


# 主线程
def main():
    t1 = Thread(target=write_data1)
    t2 = Thread(target=write_data2)
    t1.start()
    t2.start()
    t1.join()
    t2.join()
    print("主线程里看到的global_variable:", global_variable)


if __name__ == '__main__':
    main()

代码区访问临界资源

from threading import Thread

# 定义两个全局变量
value1 = 0
value2 = 0


def writer_worker():
    global value1
    global value2
    count = 0
    while 1:
        count += 1
        # 使用两句代码改变那两个全局变量
        value1 = count
        value2 = count


def check_worker():
    while 1:
        # 时间片轮算法导致value2 != value1,可以使用互斥锁可解决这一问题
        if value2 != value1:
            print(f"value1:{value1}    value2:{value2}")


if __name__ == '__main__':
    t1 = Thread(target=writer_worker, name="写线程")
    t2 = Thread(target=check_worker, name="读线程")
    t1.start()
    t2.start()

如何解决访问临界资源

  1. 使用python内置的原子结构设计临界资源。
    • list、dict的操作方法
    • queue.Queue的队列结构
  2. 使用Lock(锁)、Semaphore(信号量)。
    • Lock对象(最常用的)
    • Event对象(事件对象)
    • Timer对象(定时器对象)

线程上的Lock对象

  • 锁机制是操作系统底层提供的一种互斥访问的机制。
  • 当一个任务获取到未锁定状态的锁时,任务继续运行;
  • 当一个任务获取到以锁定状态的锁时,任务将被放入等待队列,直到这把锁被释放时被唤醒。
  • threading提供了一个Lock类,来实现了这种锁机制。

Lock代码实例

import threading

value1 = 0
value2 = 0


def writer_worker():
    global value1
    global value2
    count = 0
    while True:
        count += 1
        # 上锁
        # v_lock.acquire()
        with v_lock:
            value1 = count
            value2 = count
        # 解锁
        # v_lock.release()


def check_worker():
    while True:
        # 上锁
        v_lock.acquire()
        if value2 != value1:
            print(f"+++++v1:{value1}++v2:{value2}+++")
        # 解锁
        v_lock.release()


if __name__ == '__main__':
    t1 = threading.Thread(target=writer_worker, name="写线程")
    t2 = threading.Thread(target=check_worker, name="读线程")
    # 买了一把锁
    v_lock = threading.Lock()

    t1.start()
    t2.start()

线程共享数据(临界数据)安全问题解决

from threading import Thread, Lock

num = 0


# 定义两个函数

def add1():
    global num
    # 上锁 理论:锁住最小的单元
    lock.acquire()
    for i in range(1000000):
        num += 1
        # 第一步:num
        # 第二步: temp = num+1
        # 第三步:num = temp
    # 解锁
    lock.release()
    print(f"add1:{num}")

def add2():
    global num
    # 上锁
    lock.acquire()
    for i in range(1000000):
        num += 1
    # 解锁
    lock.release()
    print(f"add2:{num}")


# 买一把锁
lock = Lock()
# 创建线程
t1 = Thread(target=add1)
t2 = Thread(target=add2)

# 启动线程
t1.start()
t2.start()

# 等等线程结束
t1.join()
t2.join()
print(f"任务结束:{num}")

类似于事务:要么都成功,要么都失败,就像同生共死那样,保证数据的完整性

死锁

  1. 产生原因:在线程间共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待对方的资源,就会造成死锁。

  2. 产生结果:尽管死锁很少发生,但一旦发生就会造成应用的停止响应。下面看一个死锁的例子

  3. 解决方法:设置超时时间

# coding=utf-8
import threading
import time


class MyThread1(threading.Thread):
    def run(self):
        if mutexA.acquire():
            print(self.name + '----do1---up----')
            time.sleep(1)

            # 设置超时时间
            if mutexB.acquire(blocking=True, timeout=2):
                print(self.name + '----do1---down----')
                mutexB.release()
            mutexA.release()


class MyThread2(threading.Thread):
    def run(self):
        if mutexB.acquire():
            print(self.name + '----do2---up----')
            time.sleep(1)
            if mutexA.acquire():
                print(self.name + '----do2---down----')
                mutexA.release()
            mutexB.release()


mutexA = threading.Lock()
mutexB = threading.Lock()

if __name__ == '__main__':
    t1 = MyThread1()
    t2 = MyThread2()
    t1.start()
    t2.start()

线程同步

利用锁机制来解决线程同步,一旦同步,效率降低

import time
from threading import Thread, Lock


def f1():
    while True:
        if lock1.acquire():
            print(1)
            time.sleep(1)
            # 解开2号锁
            lock2.release()


def f2():
    while True:
        if lock2.acquire():
            print(2)
            time.sleep(1)
            # 解开3号锁
            lock3.release()


def f3():
    while True:
        if lock3.acquire():
            print(3)
            time.sleep(1)
            # 解开1号
            lock1.release()


# 锁
lock1 = Lock()

# 锁住2号
lock2 = Lock()
lock2.acquire()

# 锁住3号
lock3 = Lock()
lock3.acquire()

# 创建三个线程
t1 = Thread(target=f1)
t2 = Thread(target=f2)
t3 = Thread(target=f3)

# 启动线程
t1.start()
t2.start()
t3.start()

GIL

全局解释锁:为了保证线程的安全,在同一时刻,只有一个线程被CPU执行。

解决方案:调用C

分析什么时候使用多进程和多线程

多进程:CPU密集型,需要CPU参与大量运算

多线程:IO密集型 ,执行这个上任务的时候,CPU只需要很少,其它大部分时间都传输,文件传输

再加一条,如果你不知道你的代码到底算CPU密集型还是IO密集型,教你个方法:

multiprocessing这个module有一个dummy的sub module,它是基于multithread实现了multiprocessing的API。

假设你使用的是multiprocessing的Pool,是使用多进程实现了concurrency
from multiprocessing import Pool
如果把这个代码改成下面这样,就变成多线程实现concurrency
from multiprocessing.dummy import Pool

两种方式都跑一下,哪个速度快用哪个就行了。

相关文章

  • iOS多线程 NSOperation

    系列文章: 多线程 多线程 pthread、NSThread 多线程 GCD 多线程 NSOperation 多线...

  • iOS多线程 pthread、NSThread

    系列文章: 多线程 多线程 pthread、NSThread 多线程 GCD 多线程 NSOperation 多线...

  • iOS多线程: GCD

    系列文章: 多线程 多线程 pthread、NSThread 多线程 GCD 多线程 NSOperation 多线...

  • iOS多线程运用

    系列文章: 多线程 多线程 pthread、NSThread 多线程 GCD 多线程 NSOperation 多线...

  • iOS多线程基础

    系列文章: 多线程 多线程 pthread、NSThread 多线程 GCD 多线程 NSOperation 多线...

  • 多线程介绍

    一、进程与线程 进程介绍 线程介绍 线程的串行 二、多线程 多线程介绍 多线程原理 多线程的优缺点 多线程优点: ...

  • iOS进阶之多线程管理(GCD、RunLoop、pthread、

    深入理解RunLoopiOS多线程--彻底学会多线程之『GCD』iOS多线程--彻底学会多线程之『pthread、...

  • iOS多线程相关面试题

    iOS多线程demo iOS多线程之--NSThread iOS多线程之--GCD详解 iOS多线程之--NSOp...

  • 多线程之--NSOperation

    iOS多线程demo iOS多线程之--NSThread iOS多线程之--GCD详解 iOS多线程之--NSOp...

  • iOS多线程之--NSThread

    iOS多线程demo iOS多线程之--NSThread iOS多线程之--GCD详解 iOS多线程之--NSOp...

网友评论

      本文标题:多线程

      本文链接:https://www.haomeiwen.com/subject/msaxuqtx.html