线程

一、什么是线程?

  在传统的操作系统中,每个进程有一个地址空间,而且默认就是一个控制线程,进程只是用来把资源集中在一起,进程只是一个资源单位,线程是cpu的执行单位。

  多线程就是一个进程中存在多个控制线程,多个控制线程共享一个进程的内存空间。

二、线程的创建开销小

  创建进程的开销远大于线程,如果我们的软件是一个工厂,该工厂有多条流水线,流水线工作需要电源,电源只有一个(单核cpu),一个车间就是一个进程,一个车间至少一条流水线(一个进程至少一个线程),创建一个进程,就是创建一个车间(申请空间,在该空间内建至少一条流水线),而建线程,就只是在一个车间内造一条流水线,无需申请空间,所以创建开销小.

  进程与进程之间是竞争关系,线程之间是协作关系,不同的进程是竞争关系,是不同程序员写的程序运行的,360把其他进程当作病毒干死,同一个进程的线程之间是合作关系,是同一个程序写的程序内开启动,迅雷内的线程是合作关系,不会自己干自己。

三、线程与进程的区别:

  线程共享创建它的进程的地址空间; 进程有自己的地址空间。
  线程可以直接访问其进程的数据段; 进程拥有自己父进程数据段的副本。
  线程可以直接与其进程的其他线程通信; 进程必须使用进程间通信来与兄弟进程通信。
  新线程很容易创建; 新流程需要重复父流程。
  线程可以对同一进程的线程进行相当大的控制; 进程只能控制子进程。
  对主线程的更改(取消,优先级更改等)可能会影响进程的其他线程的行为; 对父进程的更改不会影响子进程。

四、为何要用多线程

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

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

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

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

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

五、全局解释器锁

  虚拟机的访问有全局解释器锁来控制,正是这个锁能保证同一个时刻只有一个线程在运行。

  在多线程环境中,python虚拟机按以下方式执行:

  a.设置GIL

  b.切换到一个线程去运行

  c.运行指定数量的字节码指令或者线程主动让出控制(可以调用time.sleep(0))

  d.把线程设置为睡眠状态

  e.解锁GIL

  f.再次重复以上的步骤

  在调用外部代码的时候,GIL将会被锁定,直到这个函数结束为止,编写扩展的程序员可以主动解锁GIL。

六、python线程模块的选择

  python提供了几个用于多线程编程的模块,包括thread/threading和Quene等。thread和threading模块允许程序员创建和管理线程。thread模块提供了基本的线程和锁的支持,threading提供了更高级别、功能更强的线程管理的功能。Queue模块允许用户创建一个可以用于多个线程之间共享数据的队列数据结构。

  避免使用thread模块,因为更高级别的threading模块更为先进,对线程的支持更为完善,而且使用thread模块里的属性有可能会与threading出现冲突;其次低级别的thread模块的同步原语很少(实际上只有一个),而threading模块则有很多;再者,thread模块中当主线程结束时,所有的线程都会被强制结束掉,没有警告也不会有正常的清除工作,至少threading模块能确保重要的子线程退出后进程才退出。  

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

  线程的创建

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()
    print('主线程')

  方式二:

from threading import Thread
import time


class Sayhi(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=Sayhi('egon')
    t.start()
    print('主线程')
七、多线程与多进程
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())

  进程与线程共享问题

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,因为同一进程内的线程之间共享进程内的数据

  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


def work():
    import time
    time.sleep(2)
    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('主进程')

  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())

八、守护线程

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

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

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

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=('bgon',))
    t.setDaemon(True)
    t.start()
    print('主程序')

  例子2:

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-------")

九、锁

import threading
R=threading.Lock()
R.acquire()
"""
对公共数据的操作
"""
R.release()

  例子:

from threading import Thread,Lock
import os,time
def work():
    global n
    lock.acquire()
    temp=n
    time.sleep(0.1)
    n=temp-1
    lock.release()
if __name__ == '__main__':
    lock=Lock()
    n=100
    l=[]
    for i in range(100):
        p=Thread(target=work)
        l.append(p)
        p.start()
    for p in l:
        p.join()

    print(n) #结果肯定为0,由原来的并发执行变成串行,牺牲了执行效率保证了数据安全
抢锁

  另一个例子:

不加锁的话并发执行,速度快,数据不安全
from threading import current_thread,Thread,Lock import os,time def task(): global n print('%s is running' %current_thread().getName()) temp=n time.sleep(0.5) n=temp-1 if __name__ == '__main__': n=100 lock=Lock() threads=[] start_time=time.time() for i in range(100): t=Thread(target=task) threads.append(t) t.start() for t in threads: t.join() stop_time=time.time() print('主:%s n:%s' %(stop_time-start_time,n))
'''
Thread-1 is running
Thread-2 is running
......
Thread-100 is running
主:0.5216062068939209 n:99

十、死锁与递归锁

  所谓死锁就是两个或两个以上的进程或线程在执行过程中,因争夺资源而造成一种互相等待的现象,若无外力作用下,将无法继续进行下去,此时系统就会处于死锁状态。

from threading import Lock as Lock
import time
mutexA=Lock()
mutexA.acquire()
mutexA.acquire()
print(123)
mutexA.release()
mutexA.release()

  解决方法:递归锁,在Python中为了支持在同一线程中多次请求同一资源,python提供了可重入锁RLock。

  这个RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次require。直到一个线程所有的acquire都被release,其他的线程才能获得资源。上面的例子如果使用RLock代替Lock,则不会发生死锁:

from threading import RLock as Lock
import time
mutexA=Lock()
mutexA.acquire()
mutexA.acquire()
print(123)
mutexA.release()
mutexA.release()

  

生前无需久睡,死后自会长眠,努力解决生活中遇到的各种问题,不畏将来,勇敢面对,加油,你是最胖的,哈哈哈
原文地址:https://www.cnblogs.com/panshao51km-cn/p/11370714.html