python进程、线程、互斥锁

目录

一、操作系统

二、多任务

三、进程

1. 什么是进程

         2.创建多进程

3.进程的状态

4.进程之间的通讯

5.进程池

四、线程

1.线程概念

2.进程和线程之间的关系

3.使用threading模块创建线程

4.使用继承方式开启线程

5.线程之间共享全局变量

6.共享全局变量的问题

7.同步异步概念

8.互斥锁

 


一、操作系统

为什么要有操作系统?

1.计算机是由一个或者多个处理器CPU,内存条,磁盘,键盘,鼠标,显示器,以及各种其他输入输出设备组成的机器。如果我们想让自己的应用程序运行在计算机上,我们需要了解

2.计算机中所有的细节例如处理器怎么运行,内存的分配等等。每位程序员不可能掌握所有系统实现的细节,并且管理优化这些部件是一件挑战性极强的工作。因此就出现了操作系统(操作系统也是一个软件)。

3.操作系统的定义:操作系统是一个用来协调、管理和控制计算机硬件和软件资源的系统程序,它位于硬件和应用程序之间。起承上启下的作用。我们写好的程序只需要给操作系统即可,

4.操作系统会给我们的程序分配内存等等一些操作。

            

二、多任务

多任务就是同一时刻多个任务同时执行,例如开演唱会时明星一边唱歌一边跳舞,开车时眼睛看路手操作方向盘。这些都是多任务场景。

对于电脑来说多任务就是同时运行多个应用程序,例如qq、微信、浏览器等等同时在电脑上运行。

1.电脑实现多任务的原理

例如qq、微信、网易云音乐播放器3个应用程序能同时运行是因为CPU在多个应用程序之间高速切换的结果,当CPU切换到了qq,就用0.01s时间(时间不确定)执行qq程序,然后再随机切换到其他应用程序在执行一段时间,CPU在多个程序之间快速往复执行,我们的肉眼根本感觉不到卡顿,导致我们的错觉感觉是同时运行的效果。如果电脑运行了多个程序有时候会出现卡顿现象是因为cup切换不过来了。

2.单核、双核CPU介绍:

单核CPU 指的是CPU中有一个核心(形象理解CPU是人的头,核心是头里面包含的大脑),用来处理程序。

双核/四核CPU 就是CPU中有2个或者4个核心,(1个脑袋中长了2个大脑或者4个大脑),相当于有2个单核CPU或者是4个单核CPU

                                                            

3.查看CPU:

电脑-->属性-->设备管理器-->处理器,有4个表示一个CPU中有4个核心。

               

           2.60GHz 表示运算速度,越高越好。例如1s中可以计算多少次。

4.在python中实现多任务有3种方式,进程、线程、协程。

三、进程

1. 什么是进程

我们想通过酷我听歌,具体的过程应该是先找到酷我应用程序,然后双击就会播放音乐。当我们双击的时候,操作系统将程序装载到内存中,操作系统为它分配资源,然后才能运行。运行起来的应用程序就称之为进程。也就是说当程序不运行的时候我们称之为程序,当程序运行起来他就是一个进程。通俗的理解就是不运行的时候是程序,运行起来就是进程。程序和进程的对应关系是:程序只有一个,但是进程可以有多个。

 

                          (进程是系统进行资源分配和调度的基本单位。)

2.创建多进程

1.不使用多进程实现控制台先打印唱歌然后在打印跳舞。

import time

def sing():
    for i in range(1,4):
        print('唱第{}首歌'.format(i))
        time.sleep(1)

def dance():
    for i in range(1,4):
        print('跳第{}个舞'.format(i))
        time.sleep(1)
        
def main():
    sing()
    dance()

if __name__ == '__main__':
    main()
'''
唱第1首歌
唱第2首歌
唱第3首歌
跳第1个舞
跳第2个舞
跳第3个舞
'''

花费了6s的时间。

2.使用进程让唱歌和跳舞一起执行。

import multiprocessing  # 多进程模块
import time


def sing():
    for i in range(1, 4):
        print('唱第{}首歌'.format(i))
        time.sleep(1)


def dance():
    for i in range(1, 4):
        print('跳第{}个舞'.format(i))
        time.sleep(1)


def main():
    p1 = multiprocessing.Process(target=sing)  # 创建对象
    p2 = multiprocessing.Process(target=dance)
    p1.start()
    p2.start()


if __name__ == '__main__':
    main()
    print('zzz')
'''
zzz
唱第1首歌
跳第1个舞
唱第2首歌
跳第2个舞
唱第3首歌
跳第3个舞
'''

花费了3s的时间,提高了程序的运行效率。 

程序理解:

主进程从main()开始执行,执行main函数体,当执行到p1.start()时,创建一个子进程,p1子进程中的代码和主进程相同,只是程序执行的开始是 sing函数体。

主进程执行到p2.start()时,同样复制一份主进程代码从dance函数体开始执行。

从上面我们可以看出来,使用进程方式实现多任务耗费的资源比较大,因为一个进程就需要使用一份系统资源。

3.进程的状态

      

在程序运行的过程中,由于被操作系统的调度算法控制,程序会进入几个状态:就绪,运行和阻塞。

(1)就绪(Ready)状态

当进程已分配到除CPU以外的所有必要的资源,只要获得处理机便可立即执行,这时的进程状态称为就绪状态。

(2)执行/运行(Running)状态当进程已获得处理机,其程序正在处理机上执行,此时的进程状态称为执行状态。

(3)阻塞(Blocked)状态正在执行的进程,由于等待某个事件发生而无法执行时,便放弃处理机而处于阻塞状态。引起进程阻塞的事件可有多种,例如,等待I/O完成、申请缓冲区不能满足、等待信件(信号)等。

4.进程之间的通讯

刚才我们说了进程可以理解为复制了一份程序有加载到了内存了,进程之间是独立的,如果我想两个进程之间进行通讯怎么办呢?我们可以使用Queue 队列,队列是一种先进先出的存储数据结构,就比如排队上厕所一个道理。

两个进程通讯,就是一个子进程往queue中写内容,另一个进程从queue中取出数据。就实现了进程间的通讯了。

(1)队列

1. 创建 queue队列对象

q = multiprocessing.Queue(3)  # 3表示只能存放3个数据

参数 :maxsize是队列中允许的最大项数。如果省略此参数,则无大小限制。

返回值q 是队列对象

2. put()方法 ,向队列中存放数据。如果队列已满,此方法将阻塞至有空间可用为止。

3. get()返回q中的一个项目。如果q为空,此方法将阻塞,直到队列中有项目可用为止。

4. get_nowait(): 不等待,直接抛出异常

5. full()如果q已满,返回为True

6. q.empty() 如果调用此方法时 q为空,返回True。

import multiprocessing

# 可以写参数,如果不写表示可以放任意多个数据
q = multiprocessing.Queue(3) # 创建队列
q.put('哈哈') # 向队列中放入数据
q.put(222)
q.put([1, 2, 3])
# q.put('kk')  # 阻塞。直到有数据被使用后,才能放入。否则一直等待
# q.put_nowait('aaa')  # 抛出异常queue.Full
print(q.get())
print(q.get())
print(q.get())
# print(q.get())  #  空 队列中没有数据,等待队列放入数据

# q.get_nowait()  # queue.Empty

print(q.empty())  # 判断队列是否是空
print(q.full())  # 判断队列是否为满

练习1:使用queue模拟多任务下载和处理数据 

from multiprocessing import Process, Queue
import time

def download_data(q):
    lst = ['a', 'b', 'c']
    for i in lst:
        q.put(i)  # 将下载的数据保存到队列中
    print('下载完数据了...')
def process_data(q):
    for i in range(q.qsize()):
        print(q.get())
    print('处理完了数据...')

def main():
    q = Queue()
    p1 = Process(target=download_data, args=(q,))
    p2 = Process(target=process_data, args=(q,))
    p1.start()
    time.sleep(0.5)
    p2.start()


if __name__ == '__main__':
    main()
'''
下载完数据了...
a
b
c
处理完了数据...
'''

5.进程池

当需要创建的子进程数量不多时,我们可以直接利用multiporcessing中的Process动态生成多个进程,但是如果现在有100个任务需要处理,那我们需要多少个子进程呢,如果我们创建100个子进程也可以实现,但是资源比较浪费。我们也可以创建指定个数个子进程,例如只创建10个子进程,让着10个子进程重复的执行任务,这样就节约了资源。

就比如我们去景区湖上游玩,游船是重复利用的。

我们可以使用multiprocessing模块提供的Pool类,也就是进程池,可以到达进程重复利用。泸沽岛

创建进程池对象的时候可以指定一个最大进程数,当有新的请求提交到进程池中,如果池中的进程数还没有满,那么就会创建一个新的进程用来执行该请求,但是如果池中的进程数满了,该请求就会等待,知道进程池中的进程有结束的了,才会使用这个结束的进程来执行新的任务。

join 主进程等待所有子进程执行完毕,必须在close之后。

close 等待所有进程结束才关闭线程池

from multiprocessing import Pool
import time


def fool(i):
    print('i的值是', i)
    time.sleep(3)
    print('end')


if __name__ == '__main__':
    # for i in range(5):
    #     p = multiprocessing.Process(target=fool,args=(i,))
    #     p.start()
    pool = Pool(3)
    for i in range(5):
        pool.apply_async(func=fool, args=(i,))

    pool.close()  # 关闭进程池,等待所有进程结束
    pool.join()  # 等待所有子进程结束后,主进程才往下执行
    print('let us stop')
'''
i的值是 0
i的值是 1
i的值是 2
end
i的值是 3
end
i的值是 4
end
end
end
let us stop
'''

四、线程

1.线程概念

由于进程是资源拥有者,创建、撤消与切换存在较大的内存开销,因此需要引入轻型进程,即线程。

进程是资源分配的最小单位,线程是CPU调度的最小单位(程序真正执行的时候调用的是线程).每一个进程中至少有一个线程。 

2.进程和线程之间的关系

     

3.使用threading模块创建线程

import time
import threading  # 线程


def sing():
    for i in range(1, 4):
        print('唱第{}首歌'.format(i))
        time.sleep(1)


def dance():
    for i in range(1, 4):
        print('跳第{}个舞'.format(i))
        time.sleep(1)


def main():
    t1 = threading.Thread(target=sing)  # 创建子线程
    t2 = threading.Thread(target=dance)
    t1.start()  # 开启子线程
    t2.start()


if __name__ == '__main__':
    main()
    print('程序结束了')
'''
唱第1首歌
跳第1个舞
程序结束了
跳第2个舞
唱第2首歌
跳第3个舞
唱第3首歌
'''

执行顺序:

首先程序运行时,程序从上往下走,遇到main()函数然后开始执行,执行mian()函数的函数体时又创建了两个线程我们称之为子线程,程序运行时的线程我们称之为主线程

然后子线程根据target=xxx 开始执行指定的函数

(等子线程结束后主线程结束,程序结束了)

(1)传递参数

给函数传递参数,使用线程的关键字 args=()进行传递参数

import time
import threading  # 线程


def sing(num):
    for i in range(1,num+1):
        print('唱第{}首歌'.format(i))
        time.sleep(1)


def dance(num):
    for i in range(1,num+1):
        print('跳第{}个舞'.format(i))
        time.sleep(1)


def main():
    t1 = threading.Thread(target=sing,args=(3,))  # 创建子线程
    t2 = threading.Thread(target=dance,args=(3,))
    t1.start()  # 开启子线程
    t2.start()


if __name__ == '__main__':
    main()
    print('程序结束了')
'''
唱第1首歌
跳第1个舞
程序结束了
唱第2首歌
跳第2个舞
唱第3首歌
跳第3个舞
'''

(2)join方法

'''
join : 当前线程执行完之后才会执行其他线程
'''
import time
import threading  # 线程


def sing():
    for i in range(1, 4):
        print('唱第{}首歌'.format(i))
        time.sleep(1)


def dance():
    for i in range(1, 4):
        print('跳第{}个舞'.format(i))
        time.sleep(1)


def main():
    t1 = threading.Thread(target=sing)  # 创建子线程
    t2 = threading.Thread(target=dance)
    t1.start()  # 开启子线程
    t1.join()
    t2.start()
    # t1.join()  # 当t1和t2结束才执行主进程

if __name__ == '__main__':
    main()
    print('程序结束了')
'''
唱第1首歌
唱第2首歌
唱第3首歌
跳第1个舞
程序结束了
跳第2个舞
跳第3个舞
'''

(3) setDaemon方法

import time
import threading  # 线程


def sing():
    for i in range(1, 4):
        print('唱第{}首歌'.format(i))
        time.sleep(1)


def dance():
    for i in range(1, 4):
        print('跳第{}个舞'.format(i))
        time.sleep(1)


def main():
    t1 = threading.Thread(target=sing)  # 创建子线程
    t2 = threading.Thread(target=dance)
    t1.setDaemon(True)
    # t2.setDaemon(True)  # 将t1和t2设置为守护线程
    t1.start()  # 开启子线程
    t2.start()


if __name__ == '__main__':
    main()
    print('程序结束了')
'''
唱第1首歌
跳第1个舞
程序结束了   # 主线程结束之后,守护线程跟着结束
'''

'''
子线程执行完之后主线程才能执行完
'''

 (4) 实例方法

线程对象的一些实例方法,了解即可

- getName(): 获取线程的名称。

- setName(): 设置线程的名称。

- isAlive(): 判断当前线程存活状态。

import time
import threading  # 线程


def sing():
    for i in range(1, 4):
        print('唱第{}首歌'.format(i))
        time.sleep(1)


def dance():
    for i in range(1, 4):
        print('跳第{}个舞'.format(i))
        time.sleep(1)


def main():
    t1 = threading.Thread(target=sing)  # 创建子线程
    t2 = threading.Thread(target=dance)
    print(t1.is_alive())
    t1.start()  # 开启子线程
    t2.start()
    t1.setName('张')  # 设置线程名称
    t2.setName('刘备')
    print(t1.getName())  # 读取线程名称
    print(t2.getName())
    print(t1.is_alive())


if __name__ == '__main__':
    main()
    print('程序结束了')
'''
False
唱第1首歌
跳第1个舞
张
刘备
True
程序结束了
唱第2首歌
跳第2个舞
跳第3个舞
唱第3首歌
'''

(5)threading模块提供的方法

threading.currentThread(): 返回当前的线程变量。

threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。

threading.activeCount():

返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。

import time
import threading  # 线程


def sing():
    for i in range(1, 4):
        print('唱第{}首歌'.format(i))
        time.sleep(1)


def dance():
    for i in range(1, 4):
        print('跳第{}个舞'.format(i))
        time.sleep(1)


def main():
    t1 = threading.Thread(target=sing)  # 创建子线程
    t2 = threading.Thread(target=dance)
    t1.start()  # 开启子线程
    t2.start()
    print(threading.current_thread()) # 返回当前线程变量
    print(threading.enumerate())  # 返回列表:活着的线程集合
    print(threading.active_count())  # 活着的数量

if __name__ == '__main__':
    main()
    print('程序结束了')
'''
唱第1首歌
跳第1个舞
<_MainThread(MainThread, started 4660)>
[<_MainThread(MainThread, started 4660)>, <Thread(Thread-1, started 4736)>, <Thread(Thread-2, started 6516)>]
3
程序结束了
唱第2首歌
跳第2个舞
唱第3首歌
跳第3个舞
'''

4.使用继承方式开启线程

  1. 定义一个类继承threading.Thread类。
  2. 复写父类的run()方法。
import threading
import time


# 继承创建线程的类
class MyThread(threading.Thread):

    def __init__(self, num):
        super().__init__()
        self.num = num

    # 复写父类的run()方法
    def run(self):
        for i in range(self.num):
            print('i-->>', i)
            time.sleep(1)


if __name__ == '__main__':
    my_thread = MyThread(3)
    my_thread.start()
'''
i-->> 0
i-->> 1
i-->> 2
'''

5.线程之间共享全局变量

import threading

g_num = 10


def test1():
    global g_num
    g_num += 1
    print('test1-->', g_num)


def test2():
    print('test2-->', g_num)


def main():
    t1 = threading.Thread(target=test1)
    t2 = threading.Thread(target=test2)
    t1.start()
    t2.start()


if __name__ == '__main__':
    main()
'''
test1--> 11
test2--> 11
'''

6.共享全局变量的问题

多线程开发的时候共享全局变量会带来资源竞争效果。也就是数据不安全。

import threading
import time

g_num = 0


def test1(num):
    global g_num
    for i in range(num):
        g_num += 1

    print('test1-->', g_num)


def test2(num):
    global g_num
    for i in range(num):
        g_num += 1

    print('test2-->', g_num)


if __name__ == '__main__':
    t1 = threading.Thread(target=test1, args=(1000000,))
    t2 = threading.Thread(target=test2, args=(1000000,))
    t1.start()
    t2.start()
'''
test1--> 1251913
test2--> 1440057
'''

7.同步异步概念

同步的意思就是协同步调,按预定的先后次序执行。例如你先说完然后我再说。

大家不要将同步理解成一起动作,同步是指协同、协助、互相配合。

例如线程同步,可以理解为线程A和B一块配合工作,A执行到一定程度时要依靠B的某个结果,于是停下来示意B执行,B执行完将结果给A,然后A继续执行。

A强依赖B(对方),A必须等到B的回复,才能做出下一步响应。即A的操作(行程)是顺序执行的,中间少了哪一步都不可以,或者说中间哪一步出错都不可以。

举个例子:

你去外地上学(人生地不熟),突然生活费不够了;此时你决定打电话回家,通知家里转生活费过来,可是当你拨出电话时,对方一直处于待接听状态(即:打不通,联系不上),为了拿到生活费,你就不停的oncall、等待,最终可能不能及时要到生活费,导致你今天要做的事都没有完成,而白白花掉了时间。

异步:

异步则相反,A并不强依赖B,A对B响应的时间也不敏感,无论B返回还是不返回,A都能继续运行;B响应并返回了,A就继续做之前的事情,B没有响应,A就做其他的事情。也就是说A不存在等待对方的概念。

举个例子:

在你打完电话发现没人接听时,猜想:对方可能在忙,暂时无法接听电话,所以你发了一条短信(或者语音留言,亦或是其他的方式)通知对方后便忙其他要紧的事了;这时你就不需要持续不断的拨打电话,还可以做其他事情;待一定时间后,对方看到你的留言便回复响应你,当然对方可能转钱也可能不转钱。但是整个一天下来,你还做了很多事情。 或者说你找室友临时借了一笔钱,又开始happy的上学时光了。

对于多线程共享全局变量计算错误的问题,我们可以使用线程同步来进行解决。

8.互斥锁

当多个线程几乎同时修改一个共享数据的时候,需要进行同步控制,线程同步能够保证多个线程安全的访问竞争资源(全局内容),最简单的同步机制就是使用互斥锁。

某个线程要更改共享数据时,先将其锁定,此时资源的状态为锁定状态,其他线程就能更改,直到该线程将资源状态改为非锁定状态,也就是释放资源,其他的线程才能再次锁定资源。互斥锁保证了每一次只有一个线程进入写入操作。从而保证了多线程下数据的安全性。

1.练习一使用互斥锁解决200万次的计算问题。

import threading
import time

g_num = 0


def test1(num):
    global g_num  
    lock.acquire()  # 上锁
    for i in range(num):
        g_num += 1
    lock.release()  # 解锁
    print('test1-->', g_num)


def test2(num):
    global g_num
    lock.acquire()
    for i in range(num):
        g_num += 1
    lock.release()
    print('test2-->', g_num)

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

if __name__ == '__main__':
    t1 = threading.Thread(target=test1, args=(1000000,))
    t2 = threading.Thread(target=test2, args=(1000000,))
    t1.start()
    t2.start()
'''
test1--> 1000000
test2--> 2000000
'''

 

猜你喜欢

转载自blog.csdn.net/weixin_42223833/article/details/86581526