19-生产者消费者模型
生产者:生成数据的任务
消费者:处理数据的任务
在并发编程的过程中,如果生产者处理速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者处理,才能继续生产数据;同样的,如果消费者处理数据的能力大于生产者,那么消费者就必须等待生产者
什么是生产者和消费者
生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题,生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者。
这个阻塞队列就是用来给生产者和消费者的
生产数据的目的就是给消费者去处理
from multiprocessing import Process,Queue
import time
# 生产者在消费者消费的同时不能生产,消费者在生产的同时不能消费
# 这样的问题会导致生产者和消费者之间彼此在互相等待
# 生产者消费者模型就是可以解决这样的问题,
# 在生产者和消费者之间建立一个容器,生产者往容器里生产,消费者去容器里取,这样就解决了耦合问题
# 好处1:解耦合
# 好处2:平衡了数据处理的时间差
# 基于队列实现生产者消费者模型
def producer(q):
for i in range(10):
res = '制造品%s' % i
time.sleep(0.5)
print('生产者生产了%s' % res)
q.put(res)
def consumer(q):
while True:
res = q.get()
if res is None: break
time.sleep(1)
print('消费者消费了%s'% res)
if __name__ == '__main__':
# 启动队列->容器
q = Queue()
#     生产者
p = Process(target=producer, args=(q, ))
#   消费者
c = Process(target=consumer, args=(q, ))
p.start()
c.start()
p.join()
# 确保所有的生产者都生产完毕后才会给消费者发送结束信号(有几个消费者就要发几个结束信号)
q.put(None)
print('主进程')

【总结】
程序中有两类角色
一类负责生产数据(生产者)
一类负责处理数据(消费者)
生产者和消费者解决的问题:
1.平衡生产者消费者的数据处理速度差
2.程序解耦合
注意:实际使用过程中并不会使用Queue的方式实现生产者和消费者模型,而是使用Rabbitmq(基于网络实现)

20-JoinableQueue的使用
from multiprocessing import Process,JoinableQueue
import time
# 生产者在消费者消费的同时不能生产,消费者在生产的同时不能消费
# 这样的问题会导致生产者和消费者之间彼此在互相等待
# 生产者消费者模型就是可以解决这样的问题,
# 在生产者和消费者之间建立一个容器,生产者往容器里生产,消费者去容器里取,这样就解决了耦合问题
# 好处1:解耦合
# 好处2:平衡了数据处理的时间差
# 基于队列实现生产者消费者模型
# 回顾守护进程
def producer(q):
for i in range(2):
res = '制造品%s' % i
time.sleep(0.5)
print('生产者生产了%s' % res)
q.put(res)
q.join()
def consumer(q):
while True:
res = q.get()
if res is None: break
time.sleep(1)
print('消费者消费了%s'% res)
q.task_done()
if __name__ == '__main__':
# 启动队列->容器,JoinableQueue可以执行Queue.join
q = JoinableQueue()
#     生产者
p = Process(target=producer, args=(q, ))
#   消费者
c = Process(target=consumer, args=(q, ))
c.daemon = True
p.start()
c.start()
p.join()
print('主进程')


21-什么是线程
线程的概念:
操作系统比作公司,那么操作系统每启动一个进程就相当于成立一个部门,
为什么需要多个进程?
不同的进程有不同的作用,数据之间是应该独立隔离处理,进程实际是一个资源单位
进程需要正常运行就必须需要至少一个线程,多个线程在一个进程内是共享进程内的数据的,进程之间是不共享数据的
开进程的开销大,因为进程是需要申请内存资源的,操作系统内就要分配,开线程只需要在已经分配好的内存中再去申请执行就可以了
为什么要用多线程?
并发进程可以处理,但是进程间是不共享数据的,这就需要对数据进行不断的互相传输,那采用线程的方案是更合理的

22-开启线程的两种方式
方式一
import time
import random
from threading import Thread
def piao(name):
print('%s piaoing' % name)
time.sleep(random.randrange(1,5))
if __name__ == '__main__':
t1 = Thread(target=piao, args=('panda',))
t1.start()
# 站在执行的角度,这个就是主线程,站在资源的角度就是主进程
print('主线程')

方式二
import time
import random
from threading import Thread
class MyThread(Thread):
def __init__(self, name):
super().__init__()
self.name = name
def run(self):
print('%s is running' % self.name)
time.sleep(random.randrange(1,5))
print('%s is ending' % self.name)
if __name__ == '__main__':
t1 = MyThread('panda')
t1.start()
print('主线程')


23-进程与线程的区别
1.开进程的开销远大于开线程的开销
进程在启动之后由于要操作系统给进程分配内存空间,所以执行的时间上没有线程那样快速
import time
from threading import Thread
def piao(name):
print('%s piaoing' % name)
time.sleep(2)
if __name__ == '__main__':
t1 = Thread(target=piao, args=('panda',))
t1.start()
# 站在执行的角度,这个就是主线程,站在资源的角度就是主进程
print('主线程')

2.同一个进程内的多个线程共享该进程的地址内存空间
from threading import Thread
from multiprocessing import Process
n = 100
def task():
global n
n = 0
if __name__ == '__main__':
# p1 = Process(target=task, )
# p1.start()
# p1.join()
t1 = Thread(target=task, )
t1.start()
t1.join()
print('主线程', n)

3.pid->进程的id号
每开一个进程就会给进程分配id号
from threading import Thread
from multiprocessing import Process,current_process
import os
def task():
print('线程%s' % os.getpid())
if __name__ == '__main__':
# p1 = Process(target=task, )
# p1.start()
# p1.join()
t1 = Thread(target=task, )
t1.start()
t1.join()
print('主', os.getpid())


24-Thread对象的其他属性或方法
1.设置线程名称
setName
2.查看线程是否存活
is_alive
3.查看当前执行线程活跃数
active_count
4.显示列表格式的,把当前活跃的线程对象放入列表里
enumerate
from threading import Thread,currentThread,active_count,enumerate
import time
def task():
print('%s is running' %currentThread().getName())
time.sleep(2)
print('%s is done' % currentThread().getName())
if __name__ == '__main__':
t = Thread(target=task, name='子线程-1')
t.start()
# 设置线程名称
# t.setName('子线程---1')
# t.join()
# 查看线程是否存活
# print(t.is_alive())
# print('主线程的名称',currentThread().getName())
# t.join()
# 查看当前执行线程活跃数
# print(active_count())
# 显示列表格式的,把当前活跃的线程对象放入列表里
print(enumerate())


25-守护线程
一个进程应该在什么时候被回收?
开进程的目的是为了执行一个命令,什么时候进程结束什么时候进程就应该结束,在进程内如果只有一个主线程的话,这个进程就应该被回收,一个进程可以开多个线程,当多个线程都结束任务的时候,那么进程就应该结束
主线程代码运行完以后,还是需要等其他的线程结束以后才能结束
【总结】
在一个进程内(只有一个线程),如果不开线程,默认就一个主线程,主线程代码运行完毕,进程就可以结束了
在一个进程内(多个线程),主线程在代码运行完以后,仍然要等其他的线程把代码运行完以后才能结束
主线程从运行角度上就代表了进程的运行周期,也就是说代码运行完毕以后要等非守护线程的其他线程都允许完毕以后才能结束,守护线程等到所有进程内所有非守护线程都结束了以后才能结束。
# 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=('panda',))
#     t.setDaemon(True)
#     t.start()
#     print('主线程')
#     print(t.is_alive())
# ----------------------------------------------------------------------
from threading import Thread
import time
def foo():
print('start-123')
time.sleep(1)
print('end-123')
def bar():
print('start-456')
time.sleep(3)
print('end-456')
if __name__ == '__main__':
t1 = Thread(target=foo)
t2 = Thread(target=bar)
t1.daemon = True
t1.start()
t2.start()
print('main-----------')


26-互斥锁
互斥锁:mutex
把并行变成串行,牺牲了执行的效率来保证数据的安全
一个进程内开启了多个线程,共享这一个进程的地址的内存空间
这意味着不需要一个进程内的多个线程是能实现数据共享的,数据共享带来的问题就是竞争,那就需要加锁
这里如果不加锁的话,所有的线程都会拿到数据之后都可以接着执行,但是这样的话数据就没法保证是安全的
#mutex
from threading import Thread,Lock
import time
n = 100
def task():
global n
mutex.acquire()
temp = n
time.sleep(0.1)
n = temp-1
mutex.release()
if __name__ == '__main__':
mutex = Lock()
t_l = []
for i in range(100):
t = Thread(target=task)
t_l.append(t)
t.start()
for t in t_l:
t.join()
print('主',n)


27-GIL的基本概念
互斥锁的概念就是将并行变成串行,牺牲效率保证了数据的安全,针对不同的数据的保护就要加不同的锁
互斥锁的精髓在于保证了局部数据是安全的
GIL就是解释器级别的一把锁,本质上就是一把互斥锁
运行一个py文件,一共产生了几步?
1.先产生一个进程,为进程分配内存空间
2.将python代码作为参数传给解释器
3.解释器收到python的参数后执行
GIL全局解释锁的作用:
1.保证了线程数据的执行安全,但是牺牲了数据的处理效率
2.对于cpython解释器来说,并不能用上多核优势(当启动了一个进程,这个进程内有多个线程,这样是不能同时执行多个线程的,只能单线程的执行)
垃圾回收线程不是一直存在的,是cpython解释器定期去销毁的

28-GIL与自定义互斥锁的区别
python有GIL全局解释锁的存在,保证了python解释器能够在一个进程内的多个线程,让线程单个单个的执行
为什么还要加互斥锁来保证数据安全呢?
GIL是解释器级别的,是保证垃圾回收机制的数据的,代码内的互斥锁是保证了代码之间的数据的安全,不同的数据级别要用不同的锁
底层原理:
分析:
1、100个线程去抢GIL锁,即抢执行权限
2、肯定有一个线程先抢到GIL(暂且称为线程1),然后开始执行,一旦执行就会拿到lock.acquire()
3、极有可能线程1还未运行完毕,就有另外一个线程2抢到GIL,然后开始运行,但线程2发现互斥锁lock还未被线程1释放,于是阻塞,被迫交出执行权限,即释放GIL
4、直到线程1重新抢到GIL,开始从上次暂停的位置继续执行,直到正常释放互斥锁lock,然后其他的线程再重复2 3 4的过程
【总结】
GIL保证了一个进程内的多个线程只能有一个线程执行,目的是为了保证垃圾回收是安全的
针对不同的数据要加不同的锁,解释器级别的锁是保护解释器级别的锁,所有的线程实际上是先拿GIL锁
首先需要明确的一点是GIL并不是Python的特性,它是在实现Python解析器(CPython)时所引入的一个概念。就好比C++是一套语言(语法)标准,但是可以用不同的编译器来编译成可执行代码。>有名的编译器例如GCC,INTEL C++,Visual C++等。Python也一样,同样一段代码可以通过CPython,PyPy,Psyco等不同的Python执行环境来执行。像其中的JPython就没有GIL。然而因为CPython是大部分环境下默认的Python执行环境。所以在很多人的概念里CPython就是Python,也就想当然的把GIL归结为Python语言的缺陷。所以这里要先明确一点:GIL并不是Python的特性,Python完全可以不依赖于GIL

29-GIL与多线程
有了GIL的存在,同一个时刻有了多个线程,那也只能有一个线程执行,如果想用多核优势怎么办?
CPU是做计算的,多个CPU提升的是计算性能,对于纯计算任务来说,CPU越多,计算的执行并发越多,执行时间就是运算时间最长的那个,如果4个CPU是纯I/O的任务,再多的CPU也没有时间上的节省
结论:
1、对计算来说,cpu越多越好,但是对于I/O来说,再多的cpu也没用
2、当然对运行一个程序来说,随着cpu的增多执行效率肯定会有所提高(不管提高幅度多大,总会有所提高)
,这是因为一个程序基本上不会是纯计算或者纯I/O,所以我们只能相对的去看一个程序到底是计算密集型还是I/O密集型,
从而进一步分析python的多线程到底有无用武之地
如果并发的多个任务是计算密集型:多进程效率高
如果并发的多个任务是I/O密集型:多线程效率高
计算密集型用多进程
from multiprocessing import Process
from threading import Thread
import os,time
def work():
res = 0
for i in range(10000000):
res *= i
if __name__ == '__main__':
l = []
print(os.cpu_count())
start = time.time()
for i in range(4):
# p = Process(target=work)
p = Thread(target=work)
l.append(p)
p.start()
for p in l:
p.join()
stop = time.time()
print('spend time %s'%(stop-start))

IO密集型用多线程

from multiprocessing import Process
from threading import Thread
import threading
import os,time
def work():
time.sleep(2)
print('===>')
if __name__ == '__main__':
l=[]
print(os.cpu_count()) #本机为4核
start=time.time()
for i in range(400):
p=Process(target=work) #耗时12s多,大部分时间耗费在创建进程上
# p=Thread(target=work) #耗时2s多
l.append(p)
p.start()
for p in l:
p.join()
stop=time.time()
print('run time is %s' %(stop-start))


30-死锁与递归锁
什么是死锁?
所谓死锁: 是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程
from threading import Thread,Lock
import time
mutexA = Lock()
mutexB = Lock()
class MyThread(Thread):
def run(self):
self.f1()
self.f2()
def f1(self):
mutexA.acquire()
print('%s 拿到了A'% self.name)
mutexB.acquire()
print('%s 拿到了B' % self.name)
mutexB.release()
mutexA.release()
def f2(self):
mutexB.acquire()
print('%s 拿到了B' % self.name)
time.sleep(0.1)
mutexA.acquire()
print('%s 拿到了A'%self.name)
mutexA.release()
mutexB.release()
if __name__ == '__main__':
for i in range(10):
t = MyThread()
t.start()

什么是递归锁?
在Python中为了支持在同一线程中多次请求同一资源,python提供了可重入锁RLock。
这个RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次require。直到一个线程所有的acquire都被release,其他的线程才能获得资源。上面的例子如果使用RLock代替Lock,则不会发生死锁,二者的区别是:递归锁可以连续acquire多次,而互斥锁只能acquire一次
# 递归锁:可以连续acquire多次,每一次acquire一次,计数器就+1,只要计数为0,才能被其他线程抢到
from threading import Thread,RLock
import time
mutexB = mutexA = RLock()
class MyThread(Thread):
def run(self):
self.f1()
self.f2()
def f1(self):
mutexA.acquire()
print('%s 拿到了A'% self.name)
mutexB.acquire()
print('%s 拿到了B' % self.name)
mutexB.release()
mutexA.release()
def f2(self):
mutexB.acquire()
print('%s 拿到了B' % self.name)
time.sleep(0.1)
mutexA.acquire()
print('%s 拿到了A'%self.name)
mutexA.release()
mutexB.release()
if __name__ == '__main__':
for i in range(10):
t = MyThread()
t.start()


31-信号量
信号量也是一把锁,可以指定信号量为5,对比互斥锁同一时间只能有一个任务抢到锁去执行,信号量同一时间可以有5个任务拿到锁去执行,如果说互斥锁是合租房屋的人去抢一个厕所,那么信号量就相当于一群人去抢公共厕所,但是公共厕所的容量是有限的,这便是信号量的大小
信号量就是可以多个线程在运行
from threading import Thread,Semaphore,currentThread
import time
import random
sm = Semaphore(5)
def task():
# print('%s in '%currentThread().getName())
with sm:
print('%s in ' % currentThread().getName())
time.sleep(random.randint(1,3))
if __name__ == '__main__':
for i in range(10):
t = Thread(target=task)
t.start()


32-Event事件
Event事件实现了线程同步
线程的一个关键特性是每个线程都是独立运行且状态不可预测。如果程序中的其 他线程需要通过判断某个线程的状态来确定自己下一步的操作,这时线程同步问题就会变得非常棘手。为了解决这些问题,我们需要使用threading库中的Event对象。 对象包含一个可由线程设置的信号标志,它允许线程等待某些事件的发生。在 初始情况下,Event对象中的信号标志被设置为假。如果有线程等待一个Event对象, 而这个Event对象的标志为假,那么这个线程将会被一直阻塞直至该标志为真。一个线程如果将一个Event对象的信号标志设置为真,它将唤醒所有等待这个Event对象的线程。如果一个线程等待一个已经被设置为真的Event对象,那么它将忽略这个事件, 继续执行
from threading import Thread, Event
import time
event = Event()
# event.wait()
# event.set()
def student(name):
print('学生%s 正在听课' % name)
event.wait(3)
print('学生%s 课间活动' % name)
def teacher(name):
print('老师%s正在上课'%name)
time.sleep(7)
event.set()
if __name__ == '__main__':
stu1 = Thread(target=student, args=('panda',))
stu2 = Thread(target=student,args=('boy',))
stu3 = Thread(target=student, args=('zombie',))
t1 = Thread(target=teacher,args=('girl',))
stu1.start()
stu2.start()
stu3.start()
t1.start()

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


33-定时器
隔多少时间去触发一个任务
定时器的作用:可以产生验证码的时候设置时间
from threading import Timer
def task(name):
print('hello %s' % name)
t = Timer(5 , task , args=('panda',))
t.start()

import random
from threading import Timer
class Code:
def __init__(self):
self.make_cache()
def make_cache(self, interval=3):
self.cache = self.make_code()
print(self.cache)
self.t = Timer(interval, self.make_cache)
self.t.start()
def make_code(self,n=4):
res = ''
for i in range(n):
s1 = str(random.randint(0,9))
s2 = chr(random.randint(65,90))
res += random.choice([s1,s2])
return res
def check(self):
while True:
code = input('请输入验证码>>').strip()
if code.upper() == self.cache:
print('验证码输入正确')
self.t.cancel()
break
obj = Code()
obj.check()


34-线程queue
进程Queue是解决进程间的数据共享问题,还能解决锁的问题
线程queue是解决了线程之间的锁的问题,提供了其他的功能
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')
'''


35-多线程实现并发的套接字通信
客户端
import socket
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(('127.0.0.1', 8080))
while True:
msg = input('>>>').strip()
if not msg:break
client.send(msg.encode('gbk'))
data = client.recv(1024)
print(data.decode('gbk'))
client.close()

服务端
import socket
from threading import Thread
def server(ip, port):
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind((ip, port))
server.listen(5)
while True:
conn, addr = server.accept()
t = Thread(target=comm, args=(conn,))
t.start()
server.close()
def comm(conn):
while True:
try:
data = conn.recv(1024)
if not data:break
conn.send(data.upper())
except ConnectionError:
break
conn.close()
if __name__ == '__main__':
server('127.0.0.1', 8080)


转载于:https://www.cnblogs.com/pandaboy1123/p/9388664.html

路飞学城Python-Day31相关推荐

  1. 路飞学城python全栈开发_[Python] 老男孩路飞学城Python全栈开发重点班 骑士计划最新100G...

    简介 老男孩&路飞学城Python全栈开发重点班课程,作为Python全栈教学系列的重头戏,投入了全新的课程研发和教学精力,也是Python骑士计划的核心教学,由ALEX老师开班镇守,一线技术 ...

  2. 路飞学城—Python爬虫实战密训班 第三章

    路飞学城-Python爬虫实战密训班 第三章 一.scrapy-redis插件实现简单分布式爬虫 scrapy-redis插件用于将scrapy和redis结合实现简单分布式爬虫:- 定义调度器- 定 ...

  3. b站路飞学城python课梨视频项目代码

    @b站路飞学城python课梨视频项目代码 # -*- coding:utf-8 -*- import requests from lxml import etree import random im ...

  4. 路飞学城python开发入门学习

    零基础参加了路飞学城python七天入门集训,由于无相关基础知识,所以笔记简陋,大家将就着看看,欢迎批评指正. 1.第一个程序: print('Hello,World") 2. python ...

  5. 路飞学城python电子书_路飞学城-Python开发集训-第一章

    路飞学城-Python开发集训-第一章 1.本章学习心得.体会 我: 间接性勤奋. 我: 学习方法论:输入--输出---纠正 我: 对对对 走出舒适区, 换圈子, 转思路,投资自我加筹码. 我: 圈子 ...

  6. 路飞学城python全栈开发_python 全栈开发,Day98(路飞学城背景,django ContentType组件,表结构讲解)...

    昨日内容回顾 1. 为什么要做前后端分离?-前后端交给不同的人来编写,职责划分明确.-API (IOS,安卓,PC,微信小程序...)-vue.js等框架编写前端时,会比之前写jQuery更简单快捷. ...

  7. 路飞学城-python爬虫密训-第三章

    (一)学习心得 其实在没有正式学习python编程语言中,就知道模块是python最重要部分之一.虽然在前面HTTP协议跟IO多路复用都没有接触学的也是一知半解,scrapy模块比resquests模 ...

  8. 路飞学城-Python爬虫实战密训-第1章

    正式的开始学习爬虫知识,Python是一门接触就会爱上的语言.路飞的课真的很棒,课程讲解不是告诉你结论,而是在告诉你思考的方法和过程. 第一章,学习了如何爬取汽车之家以及抽屉登录并点赞. 1 impo ...

  9. 路飞学城-Python 爬虫实战密训-第 1 章

    本节学习体会: 鸡汤心得: 1.时代发展太快,要不断学习新东西,武装自己,才能跟得上时代的潮流,不然就是面临被pass的命运 2.要看清楚趋势,不要闭门造车 3.学习编程语言就跟学英语一样,方法很重要 ...

  10. 路飞学城-Python爬虫集训-第一章

    自学Python的时候看了不少老男孩的视频,一直欠老男孩一个会员,现在99元爬虫集训果断参与. 非常喜欢Alex和武Sir的课,技术能力超强,当然讲着讲着就开起车来也说明他俩开车的技术也超级强! 以上 ...

最新文章

  1. Java中的ClassLoader和SPI机制
  2. python 实现 softmax
  3. 必要商城MySQL开发规范
  4. 信息学奥赛一本通(C++)在线评测系统——基础(一)C++语言——1089:数字反转
  5. docker删除本地已下载的镜像
  6. 【操作系统复习】系统调用
  7. CSS3的radial-gradient(径向渐变)
  8. qpsk的matlab平方根升余,【求助】求助:【QPSK功率谱密度的matlab的代码】 - 仿真模拟 - 小木虫 - 学术 科研 互动社区...
  9. 监听url地址栏变化
  10. 纳达尔复出迎澳网开门红 直落三盘横扫本土选手
  11. Debian9用户态下安装pipenv
  12. Django Rest framework实现流程
  13. Numpy中高维axis的操作个人理解
  14. 利用dialogArguments进行网页页面传值
  15. 如何用计算机声卡录声音,教你Win10怎么录制电脑内部声卡播放的声音-电脑怎么录音...
  16. ArcGIS介绍 coverage、shapefile 和 geodatabase 这三种矢量数据。
  17. 卫星导航术语 -- 词汇与概念解释(200条)(转自今日北斗)
  18. 科罗拉多大学波尔得分校计算机科学,科罗拉多大学波尔得分校院系设置
  19. 2019网易校招笔试题 瞌睡
  20. 嵌入式软件开发学习路线

热门文章

  1. 湖北省三甲复审评审资料(2011年版第七章--信息科相关)
  2. WinRAR文件图标问题
  3. C++中吸取C的二等公民
  4. 熟练掌握python需要多久_“熟”(shu)与“熟”(shou)的区别
  5. 一日一技:看视频用这个太爽了!自动实时翻译英语视频
  6. matlab x(n)16点DFT,数字信号处理课程设计参考题目
  7. 主数据建设的挑战与发展
  8. 21页报告揭秘华为鸿蒙OS,七年沉淀、称霸5G+IoT时代的野心 | 附下载
  9. python web开发-Python Web开发从入门到精通
  10. 解决WIN7设备和打印机里空白,0个对象,但是可以打印的办法