线程

线程

什么线程

	在传统操作系统中,每个进程有一个地址空间,而且默认就有一个控制线程。
	线程顾名思义,就是一条流水线工作的过程,一条流水线必须属于一个车间,一个车间的工作过程是一个进程。
	车间负责把资源整合到一起,是一个资源单位,而一个车间内至少有一个流水线,流水线的工作需要电源,电源就相当于cpu。
	所以,进程只是用来把资源集中到一起(进程只是一个资源单位,或者说资源集合),而线程才是cpu上的执行单位。
	多线程(即多个控制线程)的概念是,在一个进程中存在多个控制线程,多个控制线程共享该进程的地址空间,相当于一个车间内有多条流水线,都共用一个车间的资源。
  • 线程的创建开销小
	如果我们的软件是一个工厂,该工厂有多条流水线,流水线工作需要电源,电源只有一个即cpu(单核cpu)。一个车间就是一个进程,一个车间至少一条流水线(一个进程至少一个线程)
	创建一个进程,就是创建一个车间(申请空间,在该空间内建至少一条流水线)。
	而建线程,就只是在一个车间内造一条流水线,无需申请空间,所以创建开销小。
    
'''
进程之间是竞争关系,线程之间是协作关系?

车间直接是竞争/抢电源的关系,竞争(不同的进程直接是竞争关系,是不同的程序员写的程序运行的,迅雷抢占其他进程的网速,360把其他进程当做病毒干死)
一个车间的不同流水线式协同工作的关系(同一个进程的线程之间是合作关系,是同一个程序写的程序内开启动,迅雷内的线程是合作关系,不会自己干自己)
'''

为何要用多线程

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

'''
1.多线程共享一个进程的地址空间
2.线程比进程更轻量级,线程比进程更容易创建可撤销,在许多操作系统中,创建一个线程比创建一个进程要快10-100倍,在有大量线程需要动态和快速修改时,这一特性很有用。
3.若多个线程都是cpu密集型的,那么并不能获得性能上的增强,但是如果存在大量的计算和大量的I/O处理,拥有多个线程允许这些活动彼此重叠运行,从而会加快程序执行的速度。
4.在多cpu系统中,为了最大限度的利用多核,可以开启多个线程,比开进程开销要小的多。(这一条并不适用于python)
'''

开启线程的两种方式

#方式一
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('tony')
    t.start()
    print('主线程')
  • 在一个进程下开启多个线程与在一个进程下开启多个子进程的区别
# 开启速度
from threading import Thread
from multiprocessing import Process
import os

def work():
	print('hello')

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

# 主线程/主进程
# hello
# 进程编号
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())

    p1 = Process(target=work)
    p2 = Process(target=work)
    p1.start()
    p2.start()
    print('主线程/主进程pid',os.getpid())
# 同一个进程内的线程共享数据
from threading import Thread
from multiprocessing import Process

def work():
    global n
    n = 0

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

线程的其他方法

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
    '''

守护线程

运行完毕并非终止运行

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

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

2.主线程在其他非守护线程运行完毕后才算运行完毕(守护线程在此时就被回收)。因为主线程的结束意味着进程的结束,进程整体的资源都将被回收,而进程必须保证非守护线程都运行完毕后才能结束。

from threading import Thread
import time

def say(name):
    time.sleep(2)
    print('%s say hello'%name)
    
if __name__ == '__main__':
    t = Thread(target=say,args=('zkim',))
    t.setDaemon(True) # 必须在t.start()之前设置
    t.start()
    
    print('主线程')
    print(t.is_alive())
    
# 主线程
# True

死锁现象与递归锁

​ 进程也有死锁与递归锁

  • 死锁

    是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,他们都讲无法推进下去。此时程系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程

from threading import Thread, Lock
import time

mutexA = Lock()
mutexB = Lock()

class MyThread(Thread):
	def run(self):
		self.func1()
		self.func2()
	
	def func1(self):
		mutexA.acquire()
		print('%s 拿到锁A'%self.name)
	
		mutexB.acquire()
		print('%s 拿到锁B'%self.name)
	
		mutexA.release()
		mutexB.release()
		
	def func2(self):
		mutexB.acquire()
		print('%s 拿到锁B'%self.name)
		
		mutexA.acquire()
		print('%s 拿到锁A'%self.name)
        
        mutexA.release()
        mutexB.release()
 
if __name__ == '__main__':
    for i in range(10):
        t = MyThread()
        t.start()
        
'''
Thread-1 拿到锁A
Thread-1 拿到锁B
Thread-1 拿到锁B
Thread-2 拿到锁A
然后卡住,死锁了。
'''

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

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

mutexA=mutexB=threading.RLock() 
#一个线程拿到锁,counter加1,该线程内又碰到加锁的情况,则counter继续加1,这期间所有其他线程都只能等待,等待该线程释放所有锁,即counter递减到0为止

信号量Semaphore

# Semaphore管理一个内置的计数器,每当调用acquire()时内置计数器-1,调用release()时内置计数器+1;计数器不能小于0,当计数器为0时,acquire()将阻塞线程直到其他线程调用release()。

# 实例:(同时只有5个线程可以获得semaphore,既可以限制最大连接数为5):
from threading import Thread, Semaphore
import threading
import time

def func():
    sm.acquire()
    print('%s get sm'%threading.current_thread().getName())
    time.sleep(3)
    sm.release()

if __name__ == '__main__':
    sm = Semaphore(5)
    for i in range(23):
        t = Thread(target=func)
        t.start()

Event

'''
    线程的一个关键特性是每个线程都是独立运行且状态不可预测。如果程序中的其他线程需要通过判断某个线程的状态来确定自己下一步的操作,这时线程同步问题就会变得非常棘手。为了解决这些问题,我们需要使用threading库中的Event对象。 对象包含一个可由线程设置的信号标志,它允许线程等待某些事件的发生。在初始情况下,Event对象中的信号标志被设置为假。如果有线程等待一个Event对象, 而这个Event对象的标志为假,那么这个线程将会被一直阻塞直至该标志为真。一个线程如果将一个Event对象的信号标志设置为真,它将唤醒所有等待这个Event对象的线程。如果一个线程等待一个已经被设置为真的Event对象,那么它将忽略这个事件, 继续执行。 
'''

event.isSet():返回event的状态值;
event.wait():如果event.isSet()==False将阻塞线程;
event.set():设置event的状态值为True,所有阻塞池的线程激活进入就绪状态, 等待操作系统调度;
event.clear():恢复event的状态值为False

# 例如,有多个工作线程尝试链接MySQL,我们想要在链接前确保MySQL服务正常才让那些工作线程去连接MySQL服务器,如果连接不成功,都会去尝试重新连接。那么我们就可以采用threading.Event机制来协调各个工作线程的连接操作

from threading import Thread, Event
import threading
import time, random

def conn_mysql():
    count = 1
    while not event.is_set():
        if count > 3:
            raise TimeoutError('链接超时')
        print('<%s>第%s次尝试链接'%(threading.current_thread().getName(),count))
        event.wait(0.5)
        count += 1

    print('<%s>链接成功'%threading.current_thread().getName())

def check_mysql():
    print('<%s>正在检查mysql'%threading.current_thread().getName())
    time.sleep(random.randint(0,2))
    event.set()

if __name__ == '__main__':
    event = Event()
    con1 = Thread(target=conn_mysql)
    con2 = Thread(target=conn_mysql)
    check = Thread(target=check_mysql)

    con1.start()
    con2.start()
    check.start()

千里之行,始于足下。
原文地址:https://www.cnblogs.com/jincoco/p/12937081.html