python并发编程之多线程(操作篇)

目录:

  • 一 、threading模块介绍
  • 二 、开启线程的两种方式
  • 三、 在一个进程下开启多个线程与在一个进程下开启多个子进程的区别
  • 四 、练习
  • 五 、线程相关的其他方法
  • 六 、守护线程
  • 七、同一个进程下的多个线程数据是共享的
  • 八 、Python GIL(Global Interpreter Lock)
  • 九 、互斥锁
  • 十、同一个进程下的多线程无法利用多核优势,是不是就没有用了

一、threading模块介绍

multiprocess模块的完全模仿了threading模块的接口,二者在使用层面,有很大的相似性,因而不再详细介绍

二、开启线程的两种方式:

方式一:

from threading import Thread
import time

#方式一
def talk(name):
    time.sleep(1)
    print("{} age is 18".format(name))


if __name__ == '__main__':
    t1=Thread(target=talk,args=("egon",))
    t1.start()

    print("")
    
'''
主
egon age is 18
'''

方式二:

from threading import Thread
import time

class PrintAge(Thread):
    def __init__(self,name):
        super().__init__()
        self.name=name

    def run(self):
        time.sleep(1)
        print("{} age is 18".format(self.name))

if __name__ == '__main__':
    t1=PrintAge("egon")
    t1.start()
    print("")

"""
主
egon age is 18
"""

三 、在一个进程下开启多个线程与在一个进程下开启多个子进程的区别

1 谁的开启速度快

from threading import Thread
from multiprocessing import Process
import os

def work():
    print('hello')

if __name__ == '__main__':
    #在主进程下开启线程
    t=Thread(target=work)
    t.start()
    print('主线程/主进程')
    '''
    打印结果:
    hello
    主线程/主进程
    '''

    #在主进程下开启子进程
    t=Process(target=work)
    t.start()
    print('主线程/主进程')
    '''
    打印结果:
    主线程/主进程
    hello
    '''

2 瞅一瞅pid

from threading import Thread
from multiprocessing import Process
import os

def work():
    print('hello',os.getpid())

if __name__ == '__main__':
    #part1:在主进程下开启多个线程,每个线程都跟主进程的pid一样
    t1=Thread(target=work)
    t2=Thread(target=work)
    t1.start()
    t2.start()
    print('主线程/主进程pid',os.getpid())

    #part2:开多个进程,每个进程都有不同的pid
    p1=Process(target=work)
    p2=Process(target=work)
    p1.start()
    p2.start()
    print('主线程/主进程pid',os.getpid())

3 同一进程内的线程共享该进程的数据?

from  threading import Thread
from multiprocessing import Process
import os
def work():
    global n
    n=0

if __name__ == '__main__':
    # n=100
    # p=Process(target=work)
    # p.start()
    # p.join()
    # print('主',n) #毫无疑问子进程p已经将自己的全局的n改成了0,但改的仅仅是它自己的,查看父进程的n仍然为100


    n=1
    t=Thread(target=work)
    t.start()
    t.join()
    print('',n) #查看结果为0,因为同一进程内的线程之间共享进程内的数据

四、练习

TCP服务端实现并发的效果

1、用多进程实现:

#多进程
from multiprocessing import Process
import socket

phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)

phone.bind(("127.0.0.1",9000))
phone.listen(5)

#通信循环函数
def talk(conn,client_addr):
    while True:
        try:
            msg=conn.recv(1024)
            if not msg:break
            conn.send(msg.upper())
        except Exception:
            break

if __name__ == '__main__':
    while True:
        conn,client_addr=phone.accept()
        p=Process(target=talk,args=(conn,client_addr))
        p.start()
server
from socket import *

client=socket(AF_INET,SOCK_STREAM)
client.connect(('127.0.0.1',8080))


while True:
    msg=input('>>: ').strip()
    if not msg:continue

    client.send(msg.encode('utf-8'))
    msg=client.recv(1024)
    print(msg.decode('utf-8'))
client

2、多线程实现

from threading import Thread
import socket

phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)

phone.bind(("127.0.0.1",9000))
phone.listen(5)

#通信循环函数
def talk(conn,client_addr):
    while True:
        try:
            msg=conn.recv(1024)
            if not msg:break
            conn.send(msg.upper())
        except Exception:
            break

if __name__ == '__main__':
    while True:

        #多线程
        conn, client_addr = phone.accept()
        t=Thread(target=talk,args=(conn,client_addr))
        t.start()
server
from socket import *

client=socket(AF_INET,SOCK_STREAM)
client.connect(('127.0.0.1',9000))


while True:
    msg=input('>>: ').strip()
    if not msg:continue

    client.send(msg.encode('utf-8'))
    msg=client.recv(1024)
    print(msg.decode('utf-8'))
client

五、线程相关的其他方法

Thread实例对象的方法
  # isAlive(): 返回线程是否活动的。
  # getName(): 返回线程名。
  # setName(): 设置线程名。

threading模块提供的一些方法:
  # threading.currentThread(): 返回当前的线程变量。
  # threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。
  # threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。
from threading import Thread
import threading
from multiprocessing import Process
import os

def work():
    import time
    time.sleep(3)
    print(threading.current_thread().getName())


if __name__ == '__main__':
    #在主进程下开启线程
    t=Thread(target=work)
    t.start()

    print(threading.current_thread().getName())
    print(threading.current_thread()) #主线程
    print(threading.enumerate()) #连同主线程在内有两个运行的线程
    print(threading.active_count())
    print('主线程/主进程')

    '''
    打印结果:
    MainThread
    <_MainThread(MainThread, started 140735268892672)>
    [<_MainThread(MainThread, started 140735268892672)>, <Thread(Thread-1, started 123145307557888)>]
    主线程/主进程
    Thread-1
    '''

主线程等待子线程结束:(join方法)

from threading import Thread
import time
def sayhi(name):
    time.sleep(2)
    print('%s say hello' %name)

if __name__ == '__main__':
    t=Thread(target=sayhi,args=('egon',))
    t.start()
    t.join()
    print('主线程')
    print(t.is_alive())
    '''
    egon say hello
    主线程
    False
    '''

六、守护线程

无论是进程还是线程,都遵循:守护xxx会等待主xxx运行完毕后被销毁

需要强调的是:运行完毕并非终止运行

#1.对主进程来说,运行完毕指的是主进程代码运行完毕

#2.对主线程来说,运行完毕指的是主线程所在的进程内所有非守护线程统统运行完毕,主线程才算运行完毕

详细解释:

#1 主进程在其代码结束后就已经算运行完毕了(守护进程在此时就被回收),然后主进程会一直等非守护的子进程都运行完毕后回收子进程的资源(否则会产生僵尸进程),才会结束,

#2 主线程在其他非守护线程运行完毕后才算运行完毕(守护线程在此时就被回收)。因为主线程的结束意味着进程的结束,进程整体的资源都将被回收,而进程必须保证非守护线程都运行完毕后才能结束。
from threading import Thread
import time
def sayhi(name):
    time.sleep(2)
    print('%s say hello' %name)

if __name__ == '__main__':
    t=Thread(target=sayhi,args=('egon',))
    t.setDaemon(True) #必须在t.start()之前设置
    t.start()

    print('主线程')
    print(t.is_alive())
    '''
    主线程
    True
    '''

迷惑人的例子

from threading import Thread
import time
def foo():
    print(123)
    time.sleep(1)
    print("end123")

def bar():
    print(456)
    time.sleep(3)
    print("end456")


t1=Thread(target=foo)
t2=Thread(target=bar)

t1.daemon=True
t1.start()
t2.start()
print("main-------")

'''
123
456
main-------
end123
end456
'''

七、同一个进程下的多个线程数据是共享的

from threading import Thread
import time


money = 100


def task():
    global money
    money = 666
    print(money)


if __name__ == '__main__':
    t = Thread(target=task)
    t.start()
    t.join()
    print(money)

八、Python GIL(Global Interpreter Lock)

链接:http://www.cnblogs.com/linhaifeng/articles/7449853.html

"""
In CPython, the global interpreter lock, or GIL, is a mutex that prevents multiple 
native threads from executing Python bytecodes at once. This lock is necessary mainly 
because CPython’s memory management is not thread-safe. (However, since the GIL 
exists, other features have grown to depend on the guarantees that it enforces.)
"""

"""
python解释器其实有多个版本
    Cpython
    Jpython
    Pypypython
但是普遍使用的都是CPython解释器

在CPython解释器中GIL是一把互斥锁,用来阻止同一个进程下的多个线程的同时执行
    同一个进程下的多个线程无法利用多核优势!!!
    同一进程下的线程启动时会先抢GIL,当这个线程执行结束,释放GIL(在这个线程运行的时候其他线程进入等待状态)
    
因为cpython中的内存管理不是线程安全的
内存管理(垃圾回收机制)
    1.应用计数
    2.标记清楚
    3.分代回收
    
"""

重点:
1.GIL不是python的特点而是CPython解释器的特点
2.GIL是保证解释器级别的数据的安全
3.GIL会导致同一个进程下的多个线程的无法同时执行即无法利用多核优势(******)
4.针对不同的数据还是需要加不同的锁处理
5.解释型语言的通病:同一个进程下多个线程无法利用多核优势

九、互斥锁

需要注意的点:
#1.线程抢的是GIL锁,GIL锁相当于执行权限,拿到执行权限后才能拿到互斥锁Lock,其他线程也可以抢到GIL,但如果发现Lock仍然没有被释放则阻塞,即便是拿到执行权限GIL也要立刻交出来

#2.join是等待所有,即整体串行,而锁只是锁住修改共享数据的部分,即部分串行,要想保证数据安全的根本原理在于让并发变成串行,join与互斥锁都可以实现,毫无疑问,互斥锁的部分串行效率要更高

实例:

没加互斥锁前

from threading import Thread, Lock
import time

money = 100


def task():
    global money

    tmp = money
    time.sleep(0.1)
    money = tmp - 1


if __name__ == '__main__':

    t_list = []

    for i in range(100):
        t = Thread(target=task)
        t.start()
        t_list.append(t)
    for t in t_list:
        t.join()
    print(money)

"""
99
"""

加上互斥锁:

from threading import Thread, Lock
import time

money = 100


def task(mutex):
    global money
    mutex.acquire()
    tmp = money
    time.sleep(0.1)
    money = tmp - 1
    mutex.release()


if __name__ == '__main__':

    t_list = []
    mutex = Lock()
    for i in range(100):
        t = Thread(target=task, args=(mutex,))
        t.start()
        t_list.append(t)
    for t in t_list:
        t.join()
    print(money)
'''
0
'''

总结:

GIL与普通互斥锁的区别

GIL 与普通Lock是两把锁,保护的数据不一样,前者是保护解释器级别的,后者是保护用户自己开发的应用程序的数据,很明显GIL不负责这件事,只能用户自定义加锁处理,即普通互斥锁Lock

十、同一个进程下的多线程无法利用多核优势,是不是就没有用了

"""
多线程是否有用要看具体情况
单核:四个任务(IO密集型\计算密集型)
多核:四个任务(IO密集型\计算密集型)
"""
# 计算密集型   每个任务都需要10s
单核(不用考虑了)
    多进程:额外的消耗资源
  多线程:介绍开销
多核
    多进程:总耗时 10+
  多线程:总耗时 40+
# IO密集型  
多核
    多进程:相对浪费资源
  多线程:更加节省资源

代码验证:

#计算密集型

# 计算密集型
from multiprocessing import Process
from threading import Thread
import os,time


def work():
    res = 0
    for i in range(10000000):
        res *= i

if __name__ == '__main__':
    l = []
    print(os.cpu_count())  # 获取当前计算机CPU个数
    start_time = time.time()
    for i in range(12):
        p = Process(target=work)  # 1.4679949283599854
        t = Thread(target=work)  # 5.698534250259399
        t.start()
        # p.start()
        # l.append(p)
        l.append(t)
    for p in l:
        p.join()
    print(time.time()-start_time)

#IO密集型

# IO密集型
from multiprocessing import Process
from threading import Thread
import os,time


def work():
    time.sleep(2)

if __name__ == '__main__':
    l = []
    print(os.cpu_count())  # 获取当前计算机CPU个数
    start_time = time.time()
    for i in range(4000):
        # p = Process(target=work)  # 21.149890184402466
        t = Thread(target=work)  # 3.007986068725586
        t.start()
        # p.start()
        # l.append(p)
        l.append(t)
    for p in l:
        p.join()
    print(time.time()-start_time)

总结:

"""
多进程和多线程都有各自的优势
并且我们后面在写项目的时候通常可以
    多进程下面再开设多线程
这样的话既可以利用多核也可以介绍资源消耗
"""

猜你喜欢

转载自www.cnblogs.com/baicai37/p/12769378.html