线程与进程

单核心的CPU和多核心的CPU实现多任务的基本方法。

即使多核心的CPU真正实现了并行多任务的工作,但是任务的数量远多于核心数,因此,多任务一般是将多个任务轮流分配到每个核心上执行。

实现多任务的方法可以从几个方面着手:

多进程、多线程、协程、多进程+多线程

并行和并发的概念(提纲)

线程(threading)

单线程处理

import time

def eat():
    print('吃饭')
    time.sleep(5)
    print('吃饭结束')

def whtch():
    print('看手机')
    time.sleep(5)
    print('看手机结束')

# 使用单线程
def main():
    eat()
    whtch()

if __name__ == '__main__':
    print(time.time())
    main()
    print(time.time())

多线程处理

import threading
import time


def eat():
    print('吃饭')
    time.sleep(5)
    print('吃饭结束')


def whtch():
    print('看手机')
    time.sleep(5)
    print('看手机结束')


def main():
    # 创建一个子线程对象
    e = threading.Thread(target=eat)
    # 启动子线程
    e.start()
    # 又创建一个子线程对象
    w = threading.Thread(target=whtch)
    w.start()


if __name__ == '__main__':
    print(time.time())
    main()
    print(time.time())

查看程序中的所有线程数量

import threading
import time

def eat():
    print('吃饭')
    time.sleep(2)
    print('吃饭结束')

def whtch():
    print('看手机')
    time.sleep(5)
    print('看手机结束')

def main():
    e = threading.Thread(target=eat)
    e.start()
    w = threading.Thread(target=whtch)
    w.start()

if __name__ == '__main__':

    main()

    while True:
        # 查看所有的线程
        # 我们可以看到,主线程是最后结束的
        print(threading.enumerate())
        time.sleep(1)
        #if len(threading.enumerate())  <= 1:
        #    break

使用自定义类进行多线程处理

通过前面的代码,我们可以看到,如果想打开一个子线程,只需要给线程执行一个处理函数,然后启动这个线程即可。但是我们能否使用类进行多线程的编写呢?当然是可以的。

import threading
import time

# 我们自定义的类必须要继承threading.Thread
class XXX(threading.Thread):

    # 这里必须要定义一个run方法
    def run(self):
        print(threading.enumerate())
        # self.name中保存的是当前线程的名字
        print(self.name)
        time.sleep(2)

if __name__ == '__main__':

    x1 = XXX()
    x1.start()

    x2 = XXX()
    x2.start()

关于Python中全局变量的使用

在python的函数中,针对全局变量的使用:

如果使用的时候,需要使全局变量的指向发生变化,那么在使用的时候必须在全局变量前加上global

如果使用的时候,仅仅是更改了全局变量指向的值,指向并没有发生变化的话,使用的时候不需要在全局变量前加global

a = 1

def g1():
    # global a
    a = 10

g1()
# 在函数内部,如果不使用global,那么就不会使用全局变量a
print(a)
b = [20, 30]

def g2():
    b[0] = 111

g2()
print(b)

在各个线程中,全局变量可以共享

import threading
import time

num = 0

def fun1():
    global num
    for i in range(0, 500):
        num += 1

def fun2():
    global num
    for i in range(0, 500):
        num += 1

if __name__ == '__main__':

    t1 = threading.Thread(target=fun1)
    t2 = threading.Thread(target=fun2)
    t1.start()
    t2.start()
    
    time.sleep(1)
    print(num)

创建进程对象的时候,为要执行的函数传递参数

我们知道,对于单个cpu来说,其某一个时间点,只能执行一个任务,我们之所以看着好像可以执行多任务,其实是因为操作系统将我们的各个任务分成了很多小任务,这些小任务交替执行,我们看着好像是同时执行而已。

那么,我们可以想到,我们每个线程执行的是一个函数,这些函数里面有很多语句,如果语句并不是完整的分到一个小任务中,那么共享的东西可能会被覆盖赋值,因此会造成数据不准确。

import threading
import time


def eat(sec):
    print('吃饭')
    time.sleep(sec)
    print('吃饭结束')


def whtch(sec, other):
    print('看手机')
    time.sleep(sec)
    print(other)
    print('看手机结束')

if __name__ == '__main__':

    t1 = threading.Thread(target=eat, args=(1,))
    t2 = threading.Thread(target=whtch, args=(5, '和同学聊天'))
    t1.start()
    t2.start()

全局变量共享带了的问题

import threading
import time

num = 0

def fun1():
    global num
    for i in range(0, 500000):
        num += 1
    print('fun1:%s' % num)

def fun2():
    global num
    for i in range(0, 500000):
        num += 1
    print('fun2:%s' % num)

if __name__ == '__main__':

    t1 = threading.Thread(target=fun1)
    t2 = threading.Thread(target=fun2)
    t1.start()
    t2.start()

    time.sleep(3)
    print(num)

互斥锁

引入锁的概念后,对于任何一个资源来说,其可以有两个状态:锁定和非锁定。

当某个线程要更改一个数据的时候,可以先将该数据锁定,此时该数据处于锁定状态,其它的线程不能更改它,直到该线程操作完该数据,将其解锁,然后该数据就处于了非锁定状态,其它资源此时才可以使用该资源。互斥锁可以保证每次都只有一个线程执行写入操作。

import threading
import time
num = 0

# 创建一个锁
lock = threading.Lock()

def fun1():
    global num
    # 上锁
    lock.acquire()
    for i in range(0, 500000):
        num += 1
    # 解锁
    lock.release()
    print('fun1:%s' % num)

def fun2():
    global num
    # 上锁
    lock.acquire()
    for i in range(0, 500000):
        num += 1
    # 解锁
    lock.release()
    print('fun2:%s' % num)

if __name__ == '__main__':

    t1 = threading.Thread(target=fun1)
    t2 = threading.Thread(target=fun2)
    t1.start()
    t2.start()

    time.sleep(3)
    print(num)

被锁的资源应该越少越好

import threading
import time
num = 0

# 创建一个锁
lock = threading.Lock()

def fun1():
    global num
    for i in range(0, 500000):
        # 上锁
        lock.acquire()
        num += 1
        # 解锁
        lock.release()
    print('fun1:%s' % num)

def fun2():
    global num
    for i in range(0, 500000):
        # 上锁
        lock.acquire()
        num += 1
        # 解锁
        lock.release()
    print('fun2:%s' % num)

if __name__ == '__main__':

    t1 = threading.Thread(target=fun1)
    t2 = threading.Thread(target=fun2)
    t1.start()
    t2.start()

    time.sleep(3)
    print(num)

死锁

在多线程操作共享资源的时候,如果两个线程分别占有一部分资源并且同时等待对方释放资源,那么此时就造成了死锁。(一般出现在多个锁的时候)

import threading
import time

# 创建锁
lock1 = threading.Lock()
lock2 = threading.Lock()

def fun1():
    lock1.acquire()
    print('func1 开始执行')
    time.sleep(1)

    lock2.acquire()
    print('func1 执行结束')
    lock2.release()

    lock1.release()


def fun2():
    lock2.acquire()
    print('func2 开始执行')
    time.sleep(1)

    lock1.acquire()
    print('func2 执行结束')
    lock1.release()

    lock2.release()


if __name__ == '__main__':

    t1 = threading.Thread(target=fun1)
    t2 = threading.Thread(target=fun2)
    t1.start()
    t2.start()

进程(multiprocessing)

进程:正在运行着的程序,每个进程都有自己的独立的内存区域,每个内存区域内都有自己的堆、栈、数据段、代码段,因此全局变量值在各子进程中不能共享。

import multiprocessing
import time

def eat(sec):
    print('吃饭')
    time.sleep(sec)
    print('吃饭结束')


def whtch(sec, other):
    print('看手机')
    time.sleep(sec)
    print(other)
    print('看手机结束')


if __name__ == '__main__':

    # 创建进程对象
    p1 = multiprocessing.Process(target=eat, args=(2,))
    p2 = multiprocessing.Process(target=whtch, args=(5, '吹一波'))
    
    # 启动进程
    p1.start()
    p2.start()

我们可以看到,在Python中使用进程和使用线程,使用方式十分的相似

队列(Queue)

from multiprocessing import Queue

# 获取一个最大只能存5个数据的队列对象
q1 = Queue(5)

# 检验队列是否满了
print(q1.empty())

# 向队列中放数据
q1.put('大哥')
q1.put((1, 2, 3))
q1.put(10000)
q1.put(['帅哥', '美女'])
q1.put({'name': '张思睿'})
# 向队列中放数据,并且不希望等待
q1.put_nowait({'name': '张三'})

# 检验队列是否满
print(q1.full())

# 从队列中取数据
print(q1.get())
print(q1.get())
print(q1.get())
print(q1.get())
print(q1.get())
# 取数据,并且不希望等待
print(q1.get_nowait())

进程池

使用进程池可以重复使用进程池里进程。一般用户进程数量不定或者进程数量较大的时候。

在初始化进程池的时候,可以指定该池能存放的进程的最大数量,当有新任务需要使用进程的时候,如果该进程池没有满,那么会创建一个新的进程去执行该任务,如果进程池已满,那么该任务会等待着,等该进程池中有进程的任务结束的时候,会用该进程来执行新的任务。

from multiprocessing import Pool
import os, random,time

# 定义一个工作函数
def worker(xxx, yyy):
    print(xxx, random.randint(0, 100))
    # os.getpid()可以获取当前进程的id, os.getppid()可以获取当前进程的父进程id
    print(yyy, '进程id:%s' % os.getpid())
    time.sleep(1)


if __name__ == '__main__':

    # 定义一个最大只能存放5个进程的进程池
    p1 = Pool(5)

    for i in range(0, 20):
        # 调用工作函数,第一个参数表示进程执行的函数,第二个参数表示进程执行函数需要的参数
        p1.apply_async(worker, ('哈哈哈', '呵呵呵'))

    print('开始')
    # 进程池必须先调用close(),才能调用join()
    p1.close()
    # 如果一个进程调用了join,表示该进程执行完以后才会执行主进程
    p1.join()

守护进程

守护进程会随着主进程的代码的执行完毕而结束。

def eat(sec):
    print('吃饭')
    time.sleep(sec)
    print('吃饭结束')

if __name__ == '__main__':

    p1 = multiprocessing.Process(target=eat, args=(2,))
    
    # 设置该子进程为守护进程
    p1.daemon = True
    p1.start()

其它方法

# 判断子进程是否活着
p.is_alive()
# 结束子进程
p.terminate()
# 获取进程的名字
p.name


原文地址:https://www.cnblogs.com/imshun/p/10519001.html