线程

简介: 线程 注意:进程是资源分配的最小单位,线程是CPU调度的最小单位.     每一个进程中至少有一个线程。  线程与进程的区别可以归纳为以下4点:   1)地址空间和其它资源(如打开文件):进程间相互独立,同一进程的各线程间共享。
线程
注意:进程是资源分配的最小单位,线程是CPU调度的最小单位.
 
    每一个进程中至少有一个线程。 
线程与进程的区别可以归纳为以下4点:
  1)地址空间和其它资源(如打开文件):进程间相互独立,同一进程的各线程间共享。某进程内的线程在其它进程不可见。
  2)通信: 进程间通信 IPC,线程间可以直接读写进程数据段(如全局变量)来进行通信——需要 进程同步和互斥手段的辅助,以保证数据的一致性。
  3)调度和切换:线程上下文切换比进程上下文切换要快得多。
  4)在多线程操作系统中,进程不是一个可执行的实体

线程的特点

  在多线程的操作系统中,通常是在一个进程中包括多个线程,每个线程都是作为利用CPU的基本单位,是花费最小开销的实体。线程具有以下属性。
  1)轻型实体
  线程中的实体基本上不拥有系统资源,只是有一点必不可少的、能保证独立运行的资源。
  线程的实体包括程序、数据和TCB。线程是动态概念,它的动态特性由线程控制块TCB(Thread Control Block)描述。
  2)独立调度和分派的基本单位。
  在多线程OS中,线程是能独立运行的基本单位,因而也是独立调度和分派的基本单位。由于线程很“轻”,故线程的切换非常迅速且开销小(在同一进程中的)。
  3)共享进程资源。
  线程在同一进程中的各个线程,都可以共享该进程所拥有的资源,这首先表现在:所有线程都具有相同的进程id,这意味着,线程可以访问该进程的每一个内存资源;此外,还可以访问进程所拥有的已打开文件、定时器、信号量机构等。由于同一个进程内的线程共享内存和文件,所以线程之间互相通信不必调用内核。
  4 )可并发执行。
  在一个进程中的多个线程之间,可以并发执行,甚至允许在一个进程中所有线程都能并发执行;同样,不同进程中的线程也能并发执行,充分利用和发挥了处理机与外围设备并行工作的能力。
 

 多个线程共享同一个进程的地址空间中的资源,是对一台计算机上多个进程的模拟,有时也称线程为轻量级的进程。而对一台计算机上多个进程,则共享物理内存、磁盘、打印机等其他物理资源。

   多线程的运行也多进程的运行类似,是cpu在多个线程之间的快速切换。

 

线程和python全局解释器锁GIL

 

  Python代码的执行由Python虚拟机(也叫解释器主循环)来控制。Python在设计之初就考虑到要在主循环中,同时只有一个线程在执行。虽然 Python 解释器中可以“运行”多个线程,但在任意时刻只有一个线程在解释器中运行。
  对Python虚拟机的访问由全局解释器锁(GIL)来控制,正是这个锁能保证同一时刻只有一个线程在运行。

 

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

 

  a、设置 GIL;

 

  b、切换到一个线程去运行;

 

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

 

  d、把线程设置为睡眠状态;

 

  e、解锁 GIL;

 

  d、再次重复以上所有步骤。
  在调用外部代码(如 C/C++扩展函数)的时候,GIL将会被锁定,直到这个函数结束为止(由于在这期间没有Python的字节码被运行,所以不会做线程切换)编写扩展的程序员可以主动解锁GIL。

全局解释器GIL锁就是同一时间只能有一个线程去访问cpu,但是这并不是说python的效率就很低,因为只有在涉及到大量的计算需要cpu来计算数据的时候,这时候才涉及到线程去访问cpu,这种情况下可以说python的效率要比别的语言低。所以在这个时候我们一般采用多进程
但是在别的场景I/O很多的情况下,不涉及很多的cpu计算,这种情况下是可以多线程运行的。
全局解释器GIL锁只是在CPYTHON解释器中才会有,在别的解释器中是不存在的。
线程的创建threading模块
守护线程问题
from threading import Thread
import time
def func1():
    while True:
        time.sleep(1)
        print('hahaha') def func2(): time.sleep(5) print('子线程2') t1 = Thread(target=func1) t1.setDaemon(True) t1.start() Thread(target=func2).start() print('主线程')
####

主线程
hahaha
hahaha
hahaha
hahaha
子线程2

 

说明:守护线程会再非守护线程全部运行结束后才结束。

原因:因为主线程的结束意味着主进程结束,而子线程还没有结束,也就是说这时候主线程也还不能结束,守护线程也不能结束,当子线程结束后,守护线程结束,主线程结束,主进程也就结束了,然后就回收

 
解释:
1 主进程在其代码结束后就已经算运行完毕了(守护进程在此时就被回收),然后主进程会一直等非守护的子进程都运行完毕 后回收子进程的资源(否则会产生僵尸进程),才会结束, 2 主线程在其他非守护线程运行完毕后才算运行完毕(守护线程在此时就被回收)。因为主线程的结束意味着进程的结束, 进程整体的资源都将被回收,而进程必须保证非守护线程都运行完毕后才能结束




同步锁
from threading import Thread
import os,time
def work():
    global n
    temp=n
    time.sleep(0.1) n=temp-1 if __name__ == '__main__': 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) #结果可能为99
#这个结果为什么是99:因为每个线程在work函数中每次通过cpu计算出来的结果还要重新对n赋值,在写入内存中
 

 

同步锁
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,由原来的并发执行变成串行,牺牲了执行效率保证了数据安全

 

 


死锁(Lock)与递归锁(RLock)
死锁也叫互斥锁,就是在统一线程中,被同一个锁中的多个acquire 阻塞住。
from threading import Lock as Lock
import time
mutexA=Lock()
mutexA.acquire()
mutexA.acquire()
print(123)
mutexA.release()
mutexA.release()
###阻塞住,打印不出123
 
解决办法:用递归锁在Python中为了支持在同一线程中多次请求同一资源,python提供了可重入锁RLock。这个RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次require。直到一个线程所有的acquire都被release,其他的线程才能获得资源。
 
import time
from threading import Thread,RLock
fork_lock = noodle_lock = RLock()
def eat1(name):
    noodle_lock.acquire()
    print('%s 抢到了面条'%name)
    fork_lock.acquire()
    print('%s 抢到了叉子'%name) print('%s 吃面'%name) fork_lock.release() noodle_lock.release() def eat2(name): fork_lock.acquire() print('%s 抢到了叉子' % name) time.sleep(1) noodle_lock.acquire() print('%s 抢到了面条' % name) print('%s 吃面' % name) noodle_lock.release() fork_lock.release() for name in ['哪吒','egon','yuan']: t1 = Thread(target=eat1,args=(name,)) t2 = Thread(target=eat2,args=(name,)) t1.start() t2.start()
 

 

信号量
线程的信号量和进程的信号量一样,内部都有一个计数器,每当acquire 内部计数器就减1 当计数器为0的时候就阻塞,当release一次就加1 这时候就可以再执行一个进程。
from threading import Thread,Semaphore
import threading
import time
# def func():
#     if sm.acquire():
#         print (threading.currentThread().getName() + ' get semaphore')
#         time.sleep(2)
#         sm.release()
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()
最大线程数为5
信号量和池的区别就是:信号量会开启大量的线程,但是每次执行线程数是一定的。
线程池 因为线程池的数量是一定的,所以开启的线程数和执行的进程数永远是固定的
 

 

 

线程队列

 

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

 

queue is especially useful in threaded programming when information must be exchanged safely between multiple threads.

 
class queue.Queue(maxsize=0) #先进先出
 
import queue

q=queue.Queue()
q.put('first')
q.put('second')
q.put('third')

print(q.get())
print(q.get())
print(q.get())
'''
结果(先进先出):
first
second
third
'''

 

 

class queue.LifoQueue(maxsize=0) #last in fisrt out   后进先出

 
import queue

q=queue.LifoQueue()
q.put('first')
q.put('second')
q.put('third')

print(q.get())
print(q.get())
print(q.get())
'''
结果(后进先出):
third
second
first
'''
 

class queue.PriorityQueue(maxsize=0) #存储数据时可设置优先级的队列

 
import queue

q=queue.PriorityQueue()
#put进入一个元组,元组的第一个元素是优先级(通常是数字,也可以是非数字之间的比较),数字越小优先级越高
q.put((20,'a'))
q.put((10,'b'))
q.put((30,'c'))

print(q.get())
print(q.get())
print(q.get())
'''
结果(数字越小优先级越高,优先级高的优先出队):
(10, 'b')
(20, 'a')
(30, 'c')
'''
线程池
import time
from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor
def func(i):
    print(i*'*')
    time.sleep(1)
    return i**2

def callb(arg): print(arg.result()*'-') if __name__ == '__main__': # thread_pool = ThreadPoolExecutor(5) thread_pool = ThreadPoolExecutor(5) # ret_lst = [] for i in range(1,11): thread_pool.submit(func,i).add_done_callback(callb) # 相当于apply_async # ret = thread_pool.submit(func,i).add_done_callback(callable) # 相当于apply_async # ret_lst.append(ret) thread_pool.shutdown() # close+join # for ret in ret_lst: # print(ret.result()) print('wahaha') # 回调函数 # 进程池 # 线程池 # 当内存不需要共享,且高计算的时候 用进程 # 当内存需要共享,且高IO的时候 用线程 # 当并发很大的时候 # 多进程 : 多个任务 —— 进程池 :cpu个数、cpu个数+1 # 多线程 :多个任务 —— 线程池 :cpu个数*5 # 4C : 4个、5个进程 —— 20条线程/进程 : 80-100个任务 
 

 

目录
相关文章
|
2月前
|
Java Linux API
线程的认识
线程的认识
|
3月前
|
存储 安全 Java
C++线程浅谈
C++线程浅谈
|
5月前
|
C#
C#线程初步
C#线程初步
20 0
|
8月前
|
算法 安全 程序员
线程小练习
线程小练习
|
10月前
|
算法 Java
线程通过管道通信
线程通过管道通信
|
11月前
|
Java
线程理解
个人学习理解
59 0
|
C++
C++ | C++线程
c++创建线程的方式不止一种。
97 0
|
Java 调度
线程小记
线程小记
|
Java
什么是线程
什么是线程
107 0
|
存储 Linux
线程局部存储
TLS:Thread Local Storage,线程局部存储声明为TLS的变量在每个线程都会有一个副本,各个副本完全独立,每个副本的生命期与线程的生命期一样,即线程创建时创建,线程销毁时销毁。 C++11起可以使用thread_local关键字声明TLS变量,变量可以是任意类型。
2015 0