程序、进程和线程的概念、区别;使用进程、线程的意义

进程: process
线程: thread

程序并不能单独执行,只有将程序加载到内存中,系统为他分配资源后才能够执行,这种执行的程序称之为进程,也就是说进程是系统进行资源分配和调度的一个独立单位,每个进程都有自己单独的地址空间。所以说程序与进程的区别在于,程序是指令的集合,是进程运行的静态描述文本,而进程则是程序在系统上顺序执行时的动态活动

CPU负责控制管理进程,进程是操作系统分配资源(比如内存)的最基本单元,线程是操作系统能够进行调度和分派的最基本单元。
进程下管理的最底层单位是线程,线程是进程的最小执行和分配单元,不能独立运行,必须依附于进程。 在同一个进程的多个线程之间,是内存共享的。
一个程序,可以有多个进程,
一个进程,可以有多个线程,
一个进程里面至少有一个线程
线程是程序运行的最小单位
多线程和多进程最大的不同在于,多进程中,同一个变量,各自有一份拷贝存在于每个进程中,互不影响,而多线程中,所有变量都由所有线程共享,所以,任何一个变量都可以被任何一个线程修改,因此,线程之间共享数据最大的危险在于多个线程同时改一个变量,把内容给改乱了。
此外,各线程也有自己的局部变量,该局部变量只有线程自己能看见,对于同一进程的其它线程是不可访问的。这些线程的局部变量,称为ThreadLocal

一个程序运行后至少有一个进程,
一个进程中可以有多个线程。

进程的特点:
1.独立性independence
2.并发性concurrency

例子:
打开Microsoft word,这是一个程序
打开一个word文档,这是word程序创建的一个进程;
对word文档进行多种操作,如编辑文档、统计文字数目、修改页面布局、审阅批注等,
每一项操作都是一个线程。

在打开第一个word文档的同时,又打开了第二个word文档,word程序会创建第二个进程。

例子2:
打开网页浏览器,如Chrome,Firefox,IE,360浏览器,QQ浏览器,这是一个程序,
打开一个网页,创建一个进程,
再打开一个网页,创建第二个进程。
【注意,这里不同的应用程序具体实现多任务的方式不同(“单进程多线程/单进程单线程(默认)/多进程中每个进程单线程/多进程多线程”这四种方式),比如,实际上有些网页浏览器都是打开一个网页,就创建一个新线程,而不是进程。
所以这里的例子只是为简单说明,帮助大家理解,实际情况会更复杂。】

1.使用线程的意义
一般程序设计有:“单进程多线程/单进程单线程(默认)/多进程中每个进程单线程/多进程多线程”,这里只讨论“单进程多线程”
  线程在程序设计中是十分重要的,例如在电脑上看电影,就必须由一个线程播放视频,另一个线程播放音频,否则,单线程实现的话就只能先把视频播放完再播放音频,或者先把音频播放完再播放视频,这显然是不行的。
这里需要用到操作系统的知识,一个线程本身是不拥有计算机资源的,进程控制块(PCB)是计算机拥有资源的最小单位(可以理解为进程是拥有计算机资源的最小单位,所谓资源例如cpu使用权,内存空间,寄存器等)。线程只能在进程之中运行,它们根据一定策略轮换的使用进程的资源运行,因此,对于一个应用程序,我们的线程就是活在它的生命周期中,当进程撤销,线程也消失。因为线程不占据资源而是使用进程的资源,所以它的创建销毁的开销都远远小于进程,速度也更快。
  
通俗的理解:
就好比一个干活的项目团队,对于每一个进程,操作系统都会给它分配相应的系统资源,就像项目团队要干活就必须有办公场所、办公桌、办公电脑及相关配套设施等等。
进程里面的线程就好比项目团队里面的很多员工。线程使用的是进程的资源,就好比项目团队里的所有员工都是用的团队的办公场所和办公设备。
所以,操作系统创建和销毁线程的开销,要远小于进程,速度也更快。
就像项目团队里面,来一个人走一个人相对容易,
但是如果一个公司项目来了就招项目团队,团队干完活了又把整个团队都开了(相应的租来的办公场所和办公设备也都还回去了,),下次再有项目再招人租场所,这样会非常慢,开销也很大。

因为在同一个进程中,所以多个线程共享相同的内存空间,不同的线程可以存取内存中的同一个变量,所以,程序中的所有线程都可以读或写进程程序代码中声明过的全局变量,这在一方面是一种非常快捷的通信方式。(进程之间由于不共享内存空间,通信需要专门的方法)


每个线程都有他自己的一组CPU寄存器,称为线程上下文,线程上下文反映了线程上次运行该线程的CPU寄存器的状态用于恢复环境。指令指针和堆栈指针寄存器是线程上下文中两个最重要的寄存器,线程在线程上下文更是在进程上下文中运行的。
2.使用线程
  Python的标准库提供了两个模块:thread和threading,thread是低级模块,threading是高级模块,对thread进行了封装。绝大多数情况下,我们只需要使用threading这个高级模块。
以下介绍threading模块基本用法

1.返回当前运行线程的线程名
threading.current_thread().name

2.创建线程:
t = threading.Thread( target=函数名,args=传递给函数的参数 )

3.启动线程
t.start()

4.返回线程名
threading.Thread.getName(线程对象名)
【例子1】

import threading
import time
# 1.创建和启动线程print( threading.current_thread().name)  # 输出主线程的线程名
time.sleep(3)  # 主线程内运行sleep函数
t=threading.Thread( target=time.sleep ,args=(4,))  # 创建子线程
# t.start() #启动子线程t
print( threading.Thread.getName(t) )  # 主线程内输出子线程t的线程名print(threading.current_thread().name,'结束!')  # 主线程结束

threading.Thread( target=函数名,args=(i,), name=‘自定义子线程名’)
第一个参数是子线程具体运行的函数/方法名,第二个参数args是一个元组,用于存放传递给函数的参数,如果只传递一个值,就只需要i, 如果需要传递多个参数,那么还可以继续传递下去其他的参数。特别注意其中的逗号不能少,少了就不是元组了,就会报错。
第三个参数name是自定义子线程名,需要是字符串。
默认的子线程名,形式为’Thread-N’的唯一的名字,其中N 是比较小的十进制数。

上面的例子中,注意到主线程结束后,又过了一会,整个程序才结束。
但是,因为子线程运行的是sleep函数,在前台没有什么效果,下面我们将其改为自定义输出函数,进一步考察主线程和子线程运行的关系。

【例子2】
1----创建单线程

def fun(num):print("子线程%d执行"%num)# print( threading.current_thread().name )time.sleep(2)print("线程%d执行完毕" % num)# 创建1个子线程
t= threading.Thread(target=fun,args=(1,))
t.start()   # 启动子线程print('\n主线程结束!\n\n')   # 主线程在执行完for循环后,就直接结束了

2----也可以一次性创建多个线程

def fun(num):print("线程%d执行"%num)time.sleep(2)print("线程%d执行完毕" % num)for i in range(5):  # 主线程中运行for循环,创建5个子线程t= threading.Thread(target=fun,args=(i+1,))t.start()   # 每创建一个,就同时启动一个print('\n主线程结束!\n\n')   # 主线程在执行完for循环后,就直接结束了

从运行结果可见,主线程结束以后,子线程仍没有结束,
直到fun函数全部语句都运行完成后,子线程才结束。

  1. setDaemaon()函数的作用
    线程名.setDaemon(True)
    线程名.setDaemon(False) # 该参数默认是False

【例子3】

def fun(num):print("线程%d执行"%num)time.sleep(2)print("线程%d执行完毕" % num)
for i in range(5):t= threading.Thread(target=fun,args=(i+1,))# t.setDaemon(True)t.setDaemon(False)  # 该参数默认是Falset.start()print('\n主线程结束!')

setDaemon():
主线程A启动了子线程B,设置setDaemon的参数为True之后,主线程和子线程会同时运行,但主线程A结束运行后,无论子线程B结束与否,都会和主线程一起结束。

1、setDaemon() 的参数,默认是False(也就是说,主线程结束后,子线程仍旧继续运行)
2、设置了t.setDaemon(True) 时,称线程t为守护线程。
3、注意,设置t.setDaemon() 必须要在线程启动之前,即t.start之前

如果你设置一个线程为守护线程,,就表示你在说这个线程是不重要的,在进程退出的时候,
不用等待这个线程退出。
如果你的主线程在退出的时候,不用等待那些子线程完成,那就设置这些线程的daemon属性。即,在线程开始(thread.start())之前,调用setDeamon()函数,设定线程的daemon标志为True。(thread.setDaemon(True))就表示这个线程“不重要”。

如果你想等待子线程完成再退出,那就什么都不用做。,或者显示地调用thread.setDaemon(False),设置daemon的值为false。
新的子线程会继承父线程的daemon标志。整个Python会在所有的非守护线程退出后才会结束,即进程中没有非守护线程存在的时候才结束。

  1. join()函数的作用
    thread.join([timeout])方法:线程阻塞,用于等待线程终止。

【例子4】

def fun(num):print("线程%d执行"%num)time.sleep(5)print("线程%d执行完毕" % num)t= threading.Thread(target=fun,args=(1,))
# 设置setDaemon的参数为True,主线程A结束运行后,无论子线程B结束与否,都会和主线程一起结束
t.setDaemon(True)
t.start()
# t.join() print('\n主线程结束!')

join()的作用是,在子线程完成运行之前,这个子线程的父线程将一直被阻塞。
如在一个线程A中调用thread_b.join()方法,则只有在thread_b结束后,
线程A才会接着thread_b.join()往后运行。
join([timeout])里面的参数时可选的,代表线程运行的最大时间,
即如果超过这个时间,不管这个子线程有没有执行完毕都会被回收,
然后主线程或函数再接着执行。

7.为多线程创建线程列表

【例子5】

list_thread = []  # 存储线程的列表def fun(num):print("线程%d执行"%num)
time.sleep(2)print("线程%d执行完毕" % num)
for i in range(5):t= threading.Thread(target=fun,args=(i+1,))list_thread.append(t)# t.setDaemon(True)for i in list_thread :i.start()
# for i in list_thread:i.join()  # 将join函数放在线程启动的for循环中

将join函数放在线程启动的for循环中,会导致主线程顺次等待子线程。
因此,在多线程中,应另用一个for循环完成线程的join()。

for i in list_thread :i.start()for i in list_thread:   # 单独用一个for循环实现线程的joini.join()
  1. 线程锁
    threading.Lock()方法

多线程和多进程最大的不同在于,多进程中,同一个变量,各自有一份拷贝存在于每个进程中,互不影响,而多线程中,所有变量都由所有线程共享,所以,任何一个变量都可以被任何一个线程修改,因此,线程之间共享数据最大的危险在于多个线程同时改一个变量,把内容给改乱了。

由于线程之间可以共享数据,而线程交替被送上CPU运行,这时很容易出现的一个问题就是,一个全局变量在被某一个线程修改时,可能还没有达成我们想要得到的结果,就被撤下CUP。这时,下一个被送上CUP的线程也需要取得这个变量的值,这时候,这个值的结果其实并不是我们期望的那个结果了。

锁的好处就是确保了某段关键代码只能由一个线程从头到尾完整地执行,坏处当然也很多,首先是阻止了多线程并发执行,包含锁的某段代码实际上只能以单线程模式执行,效率就大大地下降了。其次,由于可以存在多个锁,不同的线程持有不同的锁,并试图获取对方持有的锁时,可能会造成死锁,导致多个线程全部挂起,既不能执行,也无法结束,只能靠操作系统强制终止。

多线程问题一般思路(步骤):

1.首先确定问题中的全局变量是什么,
即,某一任务中所共同访问(修改、读取)的变量是什么。
确定好后,定义并赋值该全局变量。

2.其次,确定该问题中的共同任务是什么,
即,多个线程所需要完成的共同的任务。
确定好后,将任务用自定义函数实现。
(1)在函数开始部分,声明需要用到的全局变量
(2)在函数中对全局变量的访问前后要加线程锁

3.创建并启动多线程

4.调试错误

进程池与线程池
进程(线程)池的意义:
1大型任务中可节省时间
2可以对进程(线程)数进行控制

为什么要使用线程池?
对于任务数量不断增加的程序,每有一个任务就生成一个线程,最终会导致线程数量的失控,例如,整站爬虫,假设初始只有一个链接a,那么,这个时候只启动一个线程,运行之后,得到这个链接对应页面上的b,c,d,,,等等新的链接,作为新任务,这个时候,就要为这些新的链接生成新的线程,线程数量暴涨。在之后的运行中,线程数量还会不停的增加,完全无法控制。所以,对于任务数量不端增加的程序,固定线程数量的线程池是必要的。
既然多线程可以缩短程序运行时间,那么,是不是线程数量越多越好呢?

显然,并不是,每一个线程的从生成到消亡也是需要时间和资源的,太多的线程会占用过多的系统资源(内存开销,cpu开销),而且生成太多的线程时间也是可观的,很可能会得不偿失。

thread.join()方法:线程阻塞

join()方法,用于等待线程终止。join()的作用是,在子线程完成运行之前,这个子线程的父线程将一直被阻塞。

线程

线程(thread)是进程(process)中的一个实体,一个进程至少包含一个线程。比如,对于视频播放器,显示视频用一个线程,播放音频用另一个线程。如果我们把进程看成一个容器,则线程是此容器的工作单位。

进程和线程的区别主要有:

进程之间是相互独立的,多进程中,同一个变量,各自有一份拷贝存在于每个进程中,但互不影响;而同一个进程的多个线程是内存共享的,所有变量都由所有线程共享;
由于进程间是独立的,因此一个进程的崩溃不会影响到其他进程;而线程是包含在进程之内的,进程中某一个线程的崩溃有可能会导致该进程的崩溃,同一进程内的其他线程也崩溃。

在 Python 中,进行多线程编程的模块有两个:thread 和 threading。其中,thread 是低级模块,threading 是高级模块,对 thread 进行了封装,一般来说,我们只需使用 threading 这个模块。

由于同一个进程之间的线程是内存共享的,所以当多个线程对同一个变量进行修改的时候,就会得到意想不到的结果。

让我们先看一个简单的例子:

from threading import Thread, current_threadnum = 0def calc():global numprint 'thread %s is running...' % current_thread().namefor _ in xrange(10000):num += 1print 'thread %s ended.' % current_thread().nameif __name__ == '__main__':print 'thread %s is running...' % current_thread().namethreads = []for i in range(5):threads.append(Thread(target=calc))threads[i].start()for i in range(5):threads[i].join()print 'global num: %d' % numprint 'thread %s ended.' % current_thread().name

在上面的代码中,我们创建了 5 个线程,每个线程对全局变量 num 进行 10000 次的 加 1 操作,这里之所以要循环 10000 次,是为了延长单个线程的执行时间,使线程执行时能出现中断切换的情况。现在问题来了,当这 5 个线程执行完毕时,全局变量的值是多少呢?是 50000 吗?

让我们看下执行结果:

thread MainThread is running…
thread Thread-34 is running…
thread Thread-34 ended.
thread Thread-35 is running…
thread Thread-36 is running…
thread Thread-37 is running…
thread Thread-38 is running…
thread Thread-35 ended.
thread Thread-38 ended.
thread Thread-36 ended.
thread Thread-37 ended.
global num: 30668
thread MainThread ended.

我们发现 num 的值是 30668,事实上,num 的值是不确定的,你再运行一遍,会发现结果变了。

原因是因为 num += 1 不是一个原子操作,也就是说它在执行时被分成若干步:

计算 num + 1,存入临时变量 tmp 中;
将 tmp 的值赋给 num.
由于线程是交替运行的,线程在执行时可能中断,就会导致其他线程读到一个脏值。

为了保证计算的准确性,我们就需要给 num += 1 这个操作加上锁。当某个线程开始执行这个操作时,由于该线程获得了锁,因此其他线程不能同时执行该操作,只能等待,直到锁被释放,这样就可以避免修改的冲突。创建一个锁可以通过 threading.Lock() 来实现,代码如下:

from threading import Thread, current_thread, Locknum = 0
lock = Lock()def calc():global numprint 'thread %s is running...' % current_thread().namefor _ in xrange(10000):lock.acquire()    # 获取锁num += 1lock.release()    # 释放锁print 'thread %s ended.' % current_thread().nameif __name__ == '__main__':print 'thread %s is running...' % current_thread().namethreads = []for i in range(5):threads.append(Thread(target=calc))threads[i].start()for i in range(5):threads[i].join()print 'global num: %d' % numprint 'thread %s ended.' % current_thread().name

让我们看下执行结果:

thread MainThread is running…
thread Thread-44 is running…
thread Thread-45 is running…
thread Thread-46 is running…
thread Thread-47 is running…
thread Thread-48 is running…
thread Thread-45 ended.
thread Thread-47 ended.
thread Thread-48 ended.
thread Thread-46 ended.
thread Thread-44 ended.
global num: 50000
thread MainThread ended.

GIL 锁
讲到 Python 中的多线程,就不得不面对 GIL 锁,GIL 锁的存在导致 Python 不能有效地使用多线程实现多核任务,因为在同一时间,只能有一个线程在运行。

GIL 全称是 Global Interpreter Lock,译为全局解释锁。早期的 Python 为了支持多线程,引入了 GIL 锁,用于解决多线程之间数据共享和同步的问题。但这种实现方式后来被发现是非常低效的,当大家试图去除 GIL 的时候,却发现大量库代码已重度依赖 GIL,由于各种各样的历史原因,GIL 锁就一直保留到现在。

小结

一个程序至少有一个进程,一个进程至少有一个线程。
进程是操作系统分配资源(比如内存)的最基本单元,线程是操作系统能够进行调度和分派的最基本单元。
在 Python 中,进行多线程编程的模块有两个:thread 和 threading。其中,thread 是低级模块,threading 是高级模块,对 thread 进行了封装,一般来说,我们只需使用 threading 这个模块。
在执行多线程操作时,注意加锁。

两个例子:

import threading
import timedef aa():print(threading.currentThread().name+"   aaa")time.sleep(3)print(threading.currentThread().name+"   bbb")thread = threading.Thread(target=aa,)thread.start()
thread.join(4)
print(threading.currentThread().name+"   111")
import threading
list_ticket=[]
lock=threading.Lock() #获得多线程锁
num=100
for i in range(1,num+1):ticket_num="0"*(len(str(num))-len(str(i)))+str(i) #001,003,013list_ticket.append(ticket_num)def seel_ticket():global list_ticketwhile len(list_ticket)>0:lock.acquire()print("正在打印票")thre = list_ticket[0]del list_ticket[0]print("出票成功,票号为:",thre )lock.release()
#新建线程
list_thread=[]
for i in range(10):thread=threading.Thread(target=seel_ticket,)list_thread.append(thread)
for i in list_thread:i.start()
i.join()

threading模块的其它函数:

threading.enumerate()
Return a list of all Thread objects currently alive
返回当前存在的所有线程对象的列表

threading.Condition()
可以把Condition理解为一把高级的琐,它提供了比Lock, RLock更高级的功能,允许我们能够控制复杂的线程同步问题。
threading.active_count()
返回当前存活的线程对象的数量(返回当前处于alive状态的Thread对象数量);通过计算len(threading.enumerate())长度而来

threading.get_ident()
返回线程pid

实例方法:
  isAlive(): 返回线程是否在运行。正在运行指启动后、终止前。
  get/setName(name): 获取/设置线程名。

【多进程和多线程】
多进程和多线程,这是实现多任务最常用的两种方式。现在,我们来讨论一下这两种方式的优缺点。
首先,要实现多任务,通常我们会设计Master-Worker模式,Master负责分配任务,Worker负责执行任务,因此,多任务环境下,通常是一个Master,多个Worker。
如果用多进程实现Master-Worker,主进程就是Master,其他进程就是Worker。
如果用多线程实现Master-Worker,主线程就是Master,其他线程就是Worker。
多进程模式最大的优点就是稳定性高,因为一个子进程崩溃了,不会影响主进程和其他子进程。(当然主进程挂了所有进程就全挂了,但是Master进程只负责分配任务,挂掉的概率低)著名的Apache最早就是采用多进程模式。
多进程模式的缺点是创建进程的代价大,在Unix/Linux系统下,用fork调用还行,在Windows下创建进程开销巨大。另外,操作系统能同时运行的进程数也是有限的,在内存和CPU的限制下,如果有几千个进程同时运行,操作系统连调度都会成问题。
多线程模式通常比多进程快一点,但是也快不到哪去,而且,多线程模式致命的缺点就是任何一个线程挂掉都可能直接造成整个进程崩溃,因为所有线程共享进程的内存。在Windows上,如果一个线程执行的代码出了问题,你经常可以看到这样的提示:“该程序执行了非法操作,即将关闭”,其实往往是某个线程出了问题,但是操作系统会强制结束整个进程。
在Windows下,多线程的效率比多进程要高,所以微软的IIS服务器默认采用多线程模式。由于多线程存在稳定性的问题,IIS的稳定性就不如Apache。为了缓解这个问题,IIS和Apache现在又有多进程+多线程的混合模式,真是把问题越搞越复杂。

AI之路(一)——程序、进程、多线程相关推荐

  1. 02 线程简介 多任务 多线程 普通方法调用和多线程 程序.进程.线程 Proces与Thread 核心概念

    线程简介 任务,进程,线程,多线程 多任务 多任务处理是指用户可以在同一时间内运行多个应用程序,每个应用程序被称作一个任务 多线程 原来是一条路,慢慢因为车太多了,道路堵塞,效率极低. 为了提高使用的 ...

  2. SAP ABAP程序优化-多线程并行处理

    转载请标明出处:http://blog.csdn.net/wanglei880526/article/details/8949754 实际项目实施过程中,我们会遇到程序性能优化的问题,这里介绍一种方法 ...

  3. C# 强制关闭当前程序进程(完全Kill掉不留痕迹)

    C# 强制关闭当前程序进程(完全Kill掉不留痕迹) 原文:C# 强制关闭当前程序进程(完全Kill掉不留痕迹) C# 强制关闭当前程序进程(完全Kill掉不留痕迹) C#代码 /// <sum ...

  4. alin的学习之路:Qt与多线程

    alin的学习之路:Qt与多线程 如果程序在进行复杂的逻辑处理过程中, 对窗口进行操作, 就会出现无响应的情况. 如何解决这样的问题与高并发的问题? 需要使用多线程. 方式1 特点:简单 创建一个自定 ...

  5. 我的AI之路(5)--如何选择和正确安装跟Tensorflow版本对应的CUDA和cuDNN版本

    最新的Tensorflow和CUDA cuDNN的对应关系可以从这里找到: https://tensorflow.google.cn/install/source https://tensorflow ...

  6. 算法学习之路和程序员(技术)学习必读书籍

    原文链接:http://lucida.me/blog/on-learning-algorithms/ 转 算法学习之路和程序员(技术)学习必读书籍 2015年05月26日 09:46:56 阅读数:1 ...

  7. Python多线程爬虫,小米应用商城app信息爬虫程序,多线程和多进程两种实现思路

    目录 小米应用商城app信息爬虫程序 1.需求分析 2.url分析 3.程序设计思路 4.程序代码 5.程序优化与升级 小米应用商城app信息爬虫程序 1.需求分析 看到小米应用的首页:http:// ...

  8. CPU核心数线程数、程序进程线程、并发并行的简单理解

    CPU核心数线程数.程序进程线程.并发并行.简单理解和区分 这篇文章是对上述感念的简单理解,想深入研究可以看看<计算机组成原理> CPU的核心数 线程数 当我们买电脑的时候,会看到CPU的 ...

  9. Linux多线程矩阵,操作系统实验(进程)多线程实现矩阵乘法

    <操作系统实验(进程)多线程实现矩阵乘法>由会员分享,可在线阅读,更多相关<操作系统实验(进程)多线程实现矩阵乘法(6页珍藏版)>请在人人文库网上搜索. 1.多线程编程实现矩阵 ...

  10. Android应用程序进程启动过程的源代码分析(1)

    Android应用程序框架层创建的应用程序进程具有两个特点,一是进程的入口函数是ActivityThread.main,二是进程天然支持Binder进程间通信机制:这两个特点都是在进程的初始化过程中实 ...

最新文章

  1. (转)Android SharedPreferences的使用
  2. 如何复位一个流的failbit和eofbit
  3. 5部高分学科纪录片,在家也能受益良多!
  4. python3怎么安装gmpy2_python2/3 模块gmpy2在linux下安装
  5. bzoj3527: [Zjoi2014]力 fft
  6. 杀毒行业暴利?8条杀毒行业之我见
  7. Linux内核学习编译流程
  8. python中素数的求法_python求质数的3种方法
  9. 算法:Reverse String(反转字符串)
  10. java基础入门(完整详细版)
  11. matlab检验贝塔分布规律,贝塔分布背后的直觉:概率的概率分布
  12. matlab 求公切线方程,【原创】绘制两圆公切线MATLAB代码
  13. BZOJ 4484: [Jsoi2015]最小表示 拓扑排序 bitset
  14. Java校招面经_校招面经:阿里天猫Java后台开发面试历程
  15. 青春不只风花雪月更当豪迈向上
  16. 日常工作记录:安卓运行时出现的Cause: Dex cannot parse version 52 byte code.问题
  17. 疑问代词which/what/who的用法
  18. 线阵相机学习笔记(一)
  19. JSON-spirit用法
  20. 跟驰理论 matlab,第5章-跟驰理论ppt课件

热门文章

  1. 自我分析--惰性的形成以及提高现有的精神状况
  2. ‘keytool‘ 不是内部或外部命令,也不是可运行的程序 或批处理文件。
  3. 【高频】单向链表按某值分成左边小,中间相等,左边大的形式
  4. php mpdf 设置字体,php 使用mpdf实现指定字段配置字体风格的方法
  5. HHU商务数据挖掘期末考点复习
  6. Linux Catalogue
  7. 并发编程 - Event Bus 设计模式
  8. python爬虫(以国家烟草网新闻为例)
  9. 车辆管理系统python_python实现汽车管理系统,python汽车管理系统
  10. HarmonyOS支持机型,股市精忠社知识百科:HarmonyOS支持90%机型