欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

很多人现在还不知道的知识点,Python多进程和多线程详解!

程序员文章站 2022-04-15 22:05:50
1 单进程单线程:一个人在一个桌子上吃菜。2单进程多线程:多个人在同一个桌子上一起吃菜。3多进程单线程:多个人每个人在自己的桌子上吃菜。 多线程的问题是多个人同时吃一道菜的时候容易发生争抢,例如两个人同时夹一个菜,一个人刚伸出筷子,结果伸到的时候已经被夹走菜了。。。此时就必须等一个人夹一口之后, ......

1 单进程单线程:一个人在一个桌子上吃菜。
2 单进程多线程:多个人在同一个桌子上一起吃菜。
3 多进程单线程:多个人每个人在自己的桌子上吃菜。

很多人现在还不知道的知识点,Python多进程和多线程详解!

多线程的问题是多个人同时吃一道菜的时候容易发生争抢,例如两个人同时夹一个菜,一个人刚伸出筷子,结果伸到的时候已经被夹走菜了。。。此时就必须等一个人夹一口之后,在还给另外一个人夹菜,也就是说资源共享就会发生冲突争抢。

很多人现在还不知道的知识点,Python多进程和多线程详解!

二、线程

threading是用来提供在一个进程内实现多线程的编程模块.

前面我们学习了多进程编程.

完成多任务, 也可以在一个进程内使用多个线程. 一个进程至少包括一个线程, 这个线程我们称之为主线程. 在主线程中开启的其他线程我们称之为子线程.

一个进程内的所有线程之间可以直接共享资源, 所以线程间的通信要比进程间通信方便了很多.

python的thread模块是比较底层的模块,python的threading模块是对thread做了一些包装的,可以更加方便的被使用

单线程示例:

import time


def say_sorry():
    print("亲爱的,我错了,我能吃饭了吗?")
    time.sleep(1)


if __name__ == "__main__":
    for i in range(5):
        say_sorry()

多线程示例:

import threading
import time


def say_sorry():
    print("亲爱的,我错了,我能吃饭了吗?")
    time.sleep(1)


if __name__ == "__main__":
    for i in range(5):
        t = threading.thread(target=say_sorry)
        t.start() #启动线程,即让线程开始执行

说明:

  1. 可以明显看出使用了多线程并发的操作,花费时间要短很多
  2. 创建好的线程,需要调用start()方法来启动

2.1 threading

python3 线程中常用的两个模块为:

  • _thread

    thread 模块已被废弃。用户可以使用 threading 模块代替。所以,在 python3 中不能再使用"thread" 模块。为了兼容性,python3 将 thread 重命名为 "_thread"

  • threading(推荐使用)

    threading用于提供线程相关的操作,线程是应用程序中工作的最小单元。

2.2 thread类

thread类表示在单独的控制线程中运行的活动。有两种方法可以指定这种活动:

2.2.1、给构造函数传递回调对象

mthread=threading.thread(target=xxxx,args=(xxxx))
mthread.start()

2.2.2、在子类中重写run() 方法  这里举个小例子:

import threading
import time
class mythread(threading.thread):
    def __init__(self,arg):
        super(mythread, self).__init__()#注意:一定要显式的调用父类的初始化函数。
        self.arg=arg
    def run(self):#定义每个线程要运行的函数
        time.sleep(1)
        print('the arg is:%s\r' % self.arg)

for i in range(4):
    t =mythread(i)
    t.start()

print('main thread end!')

2.2.3、多线程之间共享全局变量

​ 多线程和多进程最大的不同在于,多进程中,同一个变量,各自有一份拷贝存在于每个进程中,互不影响, 而多线程中,所有变量都由所有线程共享,下面观察一下两条线程*享同一份数据。

from threading import thread
import time 

g_num = 100		# 定义全局变量g_num

def work1():
    num = 1		# 定义局部变量num
    global g_num		# 关键字global标记全局变量g_num
    for i in range(3):
        g_num += 1		# 更改全局变量的值
        print("---子线程1---work1函数---g_num:%d" % g_num)

def work2():
    num = 2
    global g_num		# 关键字global标记全局变量g_num
    print("\t---子线程2---work2函数---个g_num:%d" % g_num)


if __name__ == "__main__":
    print("启动线程之前:g_num:%d" % g_num)

    t1 = thread(target=work1)    # 创建t1子线程,分配任务work1
    t2 = thread(target=work2)    # 创建t2子线程,分配任务work2
    t1.start()  	# 启动t1线程

    time.sleep(1)	# 等待t1线程执行完毕,观察t2线程中打印的全局变量是否发生改变
    t2.start() 		# 启动t2线程

很多人现在还不知道的知识点,Python多进程和多线程详解!

2.2.4、全局变量作为参数传递

将列表作为参数传递进来,在参数末尾追加元素

from threading import thread
import time

g_list = [10, 20, 30]

def work1(list):
    for i in range(3):
        list.append(i)    # 更改参数的值,在列表末尾追加元素
        print("--子线程1--work1----num:", list)

def work2(list):
    print("\t--子线程2---work2---num:", list)

if __name__ == "__main__":
    print("主线程访问g_list:" , g_list)

    t1 = thread(target=work1, args=(g_list,))    # 创建线程t1 将g_list作为参数传递进去 执行函数work1
    t2 = thread(target=work2, args=(g_list,))    # 创建线程t2 将g_list作为参数传递进去 执行函数work2
    t1.start()

    time.sleep(1)
    t2.start()

很多人现在还不知道的知识点,Python多进程和多线程详解!

将列表作为参数传递进来,将参数重置

from threading import thread
import time

g_list = [10, 20 ,30]

def work1(list):
    for i in range(3):
        # list.append(i)
        list = [1, 2, 3]	# 重置参数
        print("--子线程1--work1----num:", list)

def work2(list):
    print("\t--子线程2---work2---num:", list)

if __name__ == "__main__":
    print("主线程访问g_list:" , g_list)

    t1 = thread(target=work1, args=(g_list,))	# 创建线程t1 将g_list作为参数传递进去 执行函数work1
    t2 = thread(target=work2, args=(g_list,))	# 创建线程t2 将g_list作为参数传递进去 执行函数work2
    t1.start()  

    time.sleep(1)
    t2.start()  

很多人现在还不知道的知识点,Python多进程和多线程详解!

2.2.5、线程的锁

​ 多线程和多进程最大的不同在于,多进程中,同一个变量,各自有一份拷贝存在于每个进程中,互不影响,而多线程中,所有变量都由所有线程共享,所以,任何一个变量都可以被任何一个线程修改,因此,线程之间共享数据最大的危险在于多个线程同时改一个变量,把内容给改乱了。

来看看多个线程同时操作一个变量怎么把内容给改乱了:

import time, threading

# 假定这是你的银行存款:
balance = 0

def change_it(n):
    # 先存后取,结果应该为0:
    global balance
    balance = balance + n
    balance = balance - n

def run_thread(n):
    for i in range(100000):
        change_it(n)

if __name__ == "__main__":
	t1 = threading.thread(target=run_thread, args=(5,))
	t2 = threading.thread(target=run_thread, args=(8,))
	t1.start()
	t2.start()
    t1.join()
    t2.join()
	
	print(balance)

很多人现在还不知道的知识点,Python多进程和多线程详解!

很多人现在还不知道的知识点,Python多进程和多线程详解!

​ 每次执行的结果不一定是个啥,究其原因,是因为修改balance需要多条语句,而执行这几条语句时,线程可能中断,从而导致多个线程把同一个对象的内容改乱了。

​ 两个线程同时一存一取,就可能导致余额不对,你肯定不希望你的银行存款莫名其妙地变成了负数,所以,我们必须确保一个线程在修改balance的时候,别的线程一定不能改。

​ 如果我们要确保balance计算正确,就要给change_it()上一把锁,当某个线程开始执行change_it()时,我们说,该线程因为获得了锁,因此其他线程不能同时执行change_it(),只能等待,直到锁被释放后,获得该锁以后才能改。由于锁只有一个,无论多少线程,同一时刻最多只有一个线程持有该锁,所以,不会造成修改的冲突。创建一个锁就是通过threading.lock()来实现:

#锁的使用
mutex = threading.lock()  #创建锁
mutex.acquire([timeout])  #锁定
mutex.release()  #释放
import time, threading

balance = 0
lock = threading.lock()

def change_it(n):
    # 先存后取,结果应该为0:
    global balance
    balance = balance + n
    balance = balance - n

def run_thread(n):
    for i in range(100000):
        # 先要获取锁:
        lock.acquire()
        try:
            # 放心地改吧:
            change_it(n)
        finally:
            # 改完了一定要释放锁:
            lock.release() 
            
if __name__ == "__main__":
	t1 = threading.thread(target=run_thread, args=(5,))
	t2 = threading.thread(target=run_thread, args=(8,))
	t1.start()
	t2.start()
    t1.join()
    t2.join()
	
	print(balance)

很多人现在还不知道的知识点,Python多进程和多线程详解!