Python线程

线程

线程被称作轻量级进程。与进程类似,不过它们是在同一个进程下执行的。

并且它们会共享相同的上下文。每一个进程中至少有一个线程。

顾名思义,线程就是一条流水线工作的过程,一条流水线必须属于一个车间,一个车间的工作过程是一个进程

所以,进程只是用来把资源集中到一起(进程只是一个资源单位,或者说资源集合),而线程才是cpu上的执行单位。

 多线程(即多个控制线程)的概念是,在一个进程中存在多个控制线程,多个控制线程共享该进程的地址空间,相当于一个车间内有多条流水线,都共用一个车间的资源。(一个进程里面开多个线程(共享同一个进程里面的内存空间))

进程和线程的关系

1.创建线程比进程开销小(开一个进程,里面就有空间了,而线程在进程里面,就没必要在开一个空间了)

  如果我们的软件是一个工厂,该工厂有多条流水线,流水线工作需要电源,电源只有一个即cpu(单核cpu)

  一个车间就是一个进程,一个车间至少一条流水线(一个进程至少一个线程)创建一个进程,就是创建一个车间(申请空间,在该空间内建至少一条流水线)

  而建线程,就只是在一个车间内造一条流水线,无需申请空间,所以创建开销小

2.多线程一定是在一个进程里面开启的,共享进程里面的资源
3.线程启动的速度快
4.同一进程下的多个线程共享进程的资源,而多个进程之间内存空间是隔离的

多线程

多线程指的是,在一个进程中开启多个线程,通过Python解释器调度,简单的讲:如果多个任务共用一块地址空间,那么必须在一个进程内开启多个线程。详细的讲分为4点:

  1. 多线程共享一个进程的地址空间

  1. 线程比进程更轻量级,线程比进程更容易创建可撤销,在许多操作系统中,创建一个线程比创建一个进程要快10-100倍,在有大量线程需要动态和快速修改时,这一特性很有用

  2. 若多个线程都是cpu密集型的,那么并不能获得性能上的增强,但是如果存在大量的计算和大量的I/O处理,拥有多个线程允许这些活动彼此重叠运行,从而会加快程序执行的速度。

  3. 在多cpu系统中,为了最大限度的利用多核,可以开启多个线程,比开进程开销要小的多。(这一条并不适用于python)

Python GIL锁(CPython解释器)

Python在设计的时候,还没有多核处理器的概念。

因此,为了设计方便与线程安全,直接设计了一个锁。

这个锁要求,任何进程中,一次只能有一个线程在执行。

因此,并不能为多个线程分配多个CPU。所以Python中的线程只能实现并发,而不能实现真正的并行。

但是Python3中的GIL锁有一个很棒的设计,在遇到阻塞(不是耗时)的时候,会自动切换线程。因此我们可以利用这种机制来有效的避开阻塞~充分利用CPU

多线程用于IO密集型,如socket,爬虫,web

Python线程模块的选择

 Python提供了几个用于多线程编程的模块,包括thread、threading和Queue等。thread和threading模块允许程序员创建和管理线程。thread模块提供了基本的线程和锁的支持,threading提供了更高级别、功能更强的线程管理的功能。Queue模块允许用户创建一个可以用于多个线程之间共享数据的队列数据结构。
  避免使用thread模块,因为更高级别的threading模块更为先进,对线程的支持更为完善,而且使用thread模块里的属性有可能会与threading出现冲突;其次低级别的thread模块的同步原语很少(实际上只有一个),而threading模块则有很多;再者,thread模块中当主线程结束时,所有的线程都会被强制结束掉,没有警告也不会有正常的清除工作,至少threading模块能确保重要的子线程退出后进程才退出。 

  thread模块不支持守护线程,当主线程退出时,所有的子线程不论它们是否还在工作,都会被强行退出。而threading模块支持守护线程,守护线程一般是一个等待客户请求的服务器,如果没有客户提出请求它就在那等着,如果设定一个线程为守护线程,就表示这个线程是不重要的,在进程退出的时候,不用等待这个线程退出。

threading模块

线程的创建

from threading import Thread
import time


def say(name):
    time.sleep(2)
    """
    threading.current_thread().name得到当前线程名
    """
    print('thread %s %s say hello' % (threading.current_thread().name, name))

if __name__ == '__main__':
    t = Thread(target=say, name='一号', args=('yang',))
    t.start()
    print('主线程')

》》输出
主线程 thread 一号 yang say hello
from threading import Thread
import time

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

    def run(self):
        time.sleep(2)
        print('%s say hello' % self.name)


if __name__ == '__main__':
    t = Say('xx')
    t.start()
    print('主线程')

》》输出

主线程
xx say hello

多进程和多线程

多线程与多进程的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)
    print('主线程/主进程pid',os.getpid())
    t1.start()
    t2.start()

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

》》
主线程/主进程pid 8448
hello 8448
hello 8448
主线程/主进程pid 8448
hello 8652
hello 8124

 多线程与多进程之间全局变量

from threading import Thread
from multiprocessing import Process
import multiprocessing
import os

def work1():
    global n
    n=0
    print('children', multiprocessing.current_process().name,n)

def work2():
    global n
    n=0
    print('children', threading.current_thread().name,n)

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


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

>>

children Process-1 0
主进程 100
children Thread-1 0
主线程 0

多线程实现并发服务器

import socket
import threading
import sys
import os
server = socket.socket()
server.bind(('127.0.0.1',9988))
server.listen(5)

def readable(conn,addr):
    """
    创建一个新的线程,负责和客户端连接
    :param conn: 
    :param addr: 
    :return: 
    """
    # sys.stdin = os.fdopen(0)
    while True:
        data = conn.recv(1024)
        if data: #
            print('{}:{}'.format(addr,data.decode()))
            # msg = input(">>>") # 自定义输入 默认子进程会关闭标准输入
            #键盘是需要独占的设备,所以默认子进程关闭了
            conn.send(data)
            # print()
        else: # 正常断开,会收到空消息(回车不算空消息)
            print('close{}'.format(addr))
            break
    conn.close()

while True:
    print('-------主线程,等待连接------')
    conn,addr = server.accept()
    print('创建一个新的线程,和客户端{}通信'.format(addr))
    client = threading.Thread(target=readable, args=((conn, addr)))
    # p.start()
    client.start()

Thread类的其他方法

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
import time

def work():
    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 5888)>
    [<_MainThread(MainThread, started 5888)>, <Thread(Thread-1, started 6152)>]
    2
    主线程
    Thread-1
    '''

守护线程

无论是进程还是线程,都遵循:守护xx会等待主xx运行完毕后被销毁。需要强调的是:运行完毕并非终止运行

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=('y',))
    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")

if __name__ == '__main__':
    t1=Thread(target=foo)
    t2=Thread(target=bar)

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

 结果

    '''
    123
    456
    main-------
    end123
    end456
    
    '''
主线程必须等待非守护线程运行完毕后,才能结束。
主线程打印main之后,t2线程还没有运行完,所以不会结束

  

线程锁与Queue:

from threading import Thread

a = 0
n = 1000000

def incr(n):
    global a
    for i in range (n):
        a += 1

def decr(n):
    global a
    for i in range (n):
        a -= 1


if __name__ == '__main__':
    # 开启两个线程
    p1 = Thread(target=incr, args=(n, ))
    p2 = Thread(target=decr, args=(n,))
    p1.start()
    p2.start()
    p1.join()
    p2.join()
    print('程序结束a=', a)
    """
    程序结束a= 254080
    共享内存间存在竞争问题
    """

使用来控制共享资源的访问

from threading import Thread, Lock

a = 0
n = 1000000
lock = Lock()

def incr(n):
    global a
    for i in range(n):
        lock.acquire()  # 获得锁
        a += 1
        lock.release()  # 释放锁

def decr(n):
    global a
    for i in range(n):
        lock.acquire()  # 获得锁
        a -= 1
        lock.release()  # 释放锁


if __name__ == '__main__':
    # 开启两个线程
    p1 = Thread(target=incr, args=(n, ))
    p2 = Thread(target=decr, args=(n,))
    p1.start()
    p2.start()
    p1.join()
    p2.join()
    print('程序结束a=', a)
    """
    程序结束a = 0
    使用锁来控制共享资源的访问
    """

queue队列 :使用import queue,用法与进程Queue一样

使用queue实现生产者和消费者模型

from threading import Thread
import queue
import random
import time


class Producer(Thread):

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

    def run(self):
        for i in range(5):
            time.sleep(1)
            item = random.randint(1, 99)
            self.queue.put(item)
            msg = 'Producer %s' % item
            print_log(msg, 'prod')


class Consumer(Thread):

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

   def run(self):
       while True:
           if not self.queue.empty():
                item = self.queue.get()
                time.sleep(1)
                msg = 'Consumer %s' %item
                print_log(msg,'con')
                queue.task_done() # 告诉队列任务执行完毕

def print_log(msg, log_type="prod"):
    if log_type == 'prod':
        print("33[32;1m%s33[0m" %msg)
    elif log_type == 'con':
        print("33[31;1m%s33[0m" %msg)

if __name__ == '__main__':
    queue = queue.Queue()
    # 开启两个线程
    p = Producer(queue)
    c = Consumer(queue)
    p.start()
    c.start()
    p.join()
    c.join()
    print('程序结束', )

线程池

主线程:相当于生产者,只管向线程池提交任务。

并不关心线程池是如何执行任务的。

因此,并不关心是哪一个线程执行的这个任务。

线程池:相当于消费者,负责接收任务,

并将任务分配到一个空闲的线程中去执行。

线程池的使用

from multiprocessing.pool import ThreadPool
import time


def worker1():
    time.sleep(2)
    print('work1 complete ')


def worker2(*args, **kwargs):
    time.sleep(2)
    print('work2 complete ', args, kwargs)


if __name__ == '__main__':
    pool = ThreadPool(5)
    pool.apply_async(worker1)
    pool.apply_async(worker2,args=(1,2),kwds={'a':3,'b':4})
    print('work submit')
    pool.close()
    pool.join()
    print('程序结束')

使用线程池,实现并发服务器

from multiprocessing.pool import ThreadPool
import socket

server = socket.socket()
server.bind(('127.0.0.1', 9988))
server.listen(6)


def worker(conn):
    while True:
        recv_data = conn.recv(1024)
        if recv_data:
            print('接收的数据是:%s' % recv_data.decode())
            conn.send(recv_data)
        else:
            conn.close()
            break


if __name__ == '__main__':
    pool = ThreadPool(5)
    while True:
        conn, addr = server.accept()
        print('Connected by2', addr)  # 输出客户端的IP地址
        pool.apply_async(worker, args=(conn,))

 

原文地址:https://www.cnblogs.com/xiao-apple36/p/8689448.html