【操作系统】第二章--进程的描述与控制--笔记与理解(2)
笔记理解之后可以进行深入解释→【操作系统】第二章–进程的描述与控制–深入与解释(2)
文章目录
- 第二章--进程的描述与控制--笔记与理解(2)
- 经典进程的同步问题
- 生产者-消费者问题
- 读者-写者问题
- 哲学家就餐问题
- 进程通信
- 进程通信类型
- 消息传递的实现方式
- 线程的基本概念
- 线程的引入
- 线程与进程的比较
- 线程的状态和线程控制块
- 线程的实现
- 线程的实现方式
- 线程的实现
- 线程的创建与终止
- 本章练习
第二章–进程的描述与控制–笔记与理解(2)
经典进程的同步问题
生产者-消费者问题
- 单生产者-单消费者-单缓冲区:系统中有一个生产者进程、一个消费者进程和一个一次只能放1个产品的缓冲区。生产者进程重复的生产产品并放入到缓冲区中;每当缓冲区中有产品时,消费者进程从缓冲区中取产品进行消费。
信号量:space,存储位置,初值1;prod,产品,初值0
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
sem_t space,prod;void *producer(void *p) {while(1) {sem_wait(&space);printf("Put a product\n");sem_post(&prod);}return NULL;
}
void *consumer(void *p) {while(1) {sem_wait(&prod);printf("Get a product\n");sem_post(&space);}return NULL;
}int main(void) {sem_init(&space,0,1);sem_init(&prod,0,0);pthread_t tid[2];pthread_create(&tid[0],NULL,producer,NULL);pthread_create(&tid[1],NULL,consumer,NULL);sem_destroy(&space);sem_destroy(&prod);pthread_join(tid[0],NULL);return 0;
}
- 运行结果:
- 单生产者-单消费者-多缓冲区
产品存储:循环队列;信号量:space,存储位置,初值N;prod,产品,初值0
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
#define N 10
sem_t space,prod;
int BUffer[N];
int in=0,out=0;void *producer(void *p) {while(1) {sem_wait(&space);printf("Put a product into Buffer[%d]\n",in);in = (in+1)%N;sem_post(&prod);}return NULL;
}
void *consumer(void *p) {while(1) {sem_wait(&prod);printf("Get a product from Buffer[%d]\n",out);out = (out+1)%N;sem_post(&space);}return NULL;
}int main(void) {sem_init(&space,0,N);sem_init(&prod,0,0);pthread_t tid[2];pthread_create(&tid[0],NULL,producer,NULL);pthread_create(&tid[1],NULL,consumer,NULL);sem_destroy(&space);sem_destroy(&prod);pthread_join(tid[0],NULL);return 0;
}
- 运行结果:
- 多生产者-多消费者
信号量:space,存储位置,初值N;prod,产品,初值0;buf,缓冲区,初值1
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
#define M 8
#define N 10
sem_t space,prod,buf;
int Buffer[N];
int in = 0, out = 0;void *producer(void *p) {while(1) {sem_wait(&space);sem_wait(&buf);printf("Put a product into Buffer[%d]\n",in);in = (in+1)%N;sem_post(&prod);sem_post(&buf);}return NULL;
}
void *consumer(void *p) {while(1) {sem_wait(&prod);sem_wait(&buf);printf("Get a product from Buffer[%d]\n",out);out = (out+1)%N;sem_post(&space);sem_post(&buf);}return NULL;
}int main(void) {sem_init(&space,0,N);sem_init(&prod,0,0);sem_init(&buf,0,1);pthread_t tid[M],tid2[M];for(int i = 0; i < M; i ++) {pthread_create(&tid[i],NULL,producer,NULL);}for(int i = 0; i < M; i ++) {pthread_create(&tid2[i],NULL,consumer,NULL);}sem_destroy(&space);sem_destroy(&prod);sem_destroy(&buf);pthread_join(tid[0],NULL);return 0;}
运行结果:
总结:
读者-写者问题
- 一个数量集(如文件)被多个并发进程(线程)共享,一些进程(线程)只读取数据集内容(读者),而另一些进程(线程)则只修改数据集内容(写者)。
访问规则:
- 允许多个读者同时读取数据集;当有读者读数据集时,不允许任何写者进程写
- 当一个写者进程写数据集期间,不允许任何其他写者进程写,也不允许任何读者进程读
- 即:“读-写互斥”,“写-写互斥”,“读-读允许”
变量:readcount,读者数;信号量:sdata,数据集,初值1;srcount,读者数变量,初值1
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
#define M 10
sem_t sdata,srcount;
int readcount = 0;void *reader(void *p) {sem_wait(&srcount);readcount++;if(readcount == 1) {sem_wait(&sdata);}sem_post(&srcount);printf("Reading..\n");sem_wait(&srcount);readcount--;if(readcount == 0) {sem_post(&sdata);}sem_post(&srcount);return NULL;
}
void *writer(void *p) {sem_wait(&sdata);printf("Writing..\n");sem_post(&sdata);return NULL;
}int main(void) {sem_init(&sdata,0,1);sem_init(&srcount,0,1);pthread_t tid[M],tid2[M];for(int i = 0; i < M; i ++) {pthread_create(&tid[i],NULL,writer,NULL);}for(int i = 0; i < M; i ++) {pthread_create(&tid2[i],NULL,reader,NULL);}sem_destroy(&sdata);sem_destroy(&srcount);pthread_join(tid[0],NULL);return 0;
}
运行结果:
存在问题:写者进程在实际应用中一般拥有更高操作优先级,但有可能长期推迟
读者-写者-写者优先
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
#define M 10
sem_t sdata,srcount;
int readcount = 0;void *reader(void *p) {sem_wait(&srcount);readcount++;if(readcount == 1) {sem_wait(&sdata);}sem_post(&srcount);printf("Reading..\n");sem_wait(&srcount);readcount--;if(readcount == 0) {sem_post(&sdata);}sem_post(&srcount);return NULL;
}
void *writer(void *p) {sem_wait(&sdata);printf("Writing..\n");sem_post(&sdata);return NULL;
}int main(void) {sem_init(&sdata,0,1);sem_init(&srcount,0,1);pthread_t tid[M],tid2[M];for(int i = 0; i < M; i ++) {pthread_create(&tid[i],NULL,writer,NULL);}for(int i = 0; i < M; i ++) {pthread_create(&tid2[i],NULL,reader,NULL);}sem_destroy(&sdata);sem_destroy(&srcount);pthread_join(tid[0],NULL);return 0;
}
- 运行结果:
哲学家就餐问题
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include <pthread.h>
#include <errno.h>
#include <math.h>
#include <unistd.h>pthread_mutex_t chop[6];
void *eat_think(void *arg) {char phi = *(char *)arg;int l,r;switch(phi) {case 'A':l = 5;r = 1;break;case 'B':l = 1;r = 2;break;case 'C':l = 2;r = 3;break;case 'D':l = 3;r = 4;break;case 'E':l = 4;r = 5;}int i;for(;;) {sleep(3);pthread_mutex_lock(&chop[l]);printf("哲学家 %c 获得了筷子 %d\n",phi,l);/*if(pthread_mutex_trylock(&chop[r]) == EBUSY) {pthread_mutex_unlock(&chop[l]);continue;}*/pthread_mutex_lock(&chop[r]);printf("哲学界 %c 获得了筷子 %d\n",phi,r);printf("哲学家 %c 正在就餐\n",phi);sleep(3);pthread_mutex_unlock(&chop[l]);printf("哲学家 %c 放下了筷子 %d\n",phi,l);pthread_mutex_unlock(&chop[r]);printf("哲学家 %c 放下了筷子 %d\n",phi,r);}
}int main() {pthread_t A,B,C,D,E;for(int i = 0; i < 5 ; i ++) {pthread_mutex_init(&chop[i],NULL);}pthread_create(&A,NULL,eat_think,"A");pthread_create(&B,NULL,eat_think,"B");pthread_create(&C,NULL,eat_think,"C");pthread_create(&D,NULL,eat_think,"D");pthread_create(&E,NULL,eat_think,"E");pthread_join(A,NULL);pthread_join(B,NULL);pthread_join(C,NULL);pthread_join(D,NULL);pthread_join(E,NULL);return 0;
}
--------------------------------------------------------------------
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include <pthread.h>
#include <errno.h>
#include <math.h>
#include <unistd.h>
#include <semaphore.h>sem_t chop[5];
void *eat_think(void *arg) {int phi = *(int *)arg;while(1) {sem_wait(&chop[phi]);printf("哲学家 %d 获得了右边的筷子 %d\n",phi,phi);sleep(3);sem_wait(&chop[(phi+1)%5]);printf("哲学家 %d 获得了左边的筷子 %d\n",phi,(phi+1)%5);sem_wait(&chop[(phi+1)%5]);printf("哲学家 %d 放下了左边的筷子 %d\n",phi,(phi+1)%5);sem_wait(&chop[phi]);printf("哲学家 %d 放下了右边的筷子 %d\n",phi,phi);/*if(pthread_mutex_trylock(&chop[r]) == EBUSY) {pthread_mutex_unlock(&chop[l]);continue;}pthread_mutex_lock(&chop[r]);printf("哲学界 %c 获得了筷子 %d\n",phi,r);printf("哲学家 %c 正在就餐\n",phi);sleep(3);pthread_mutex_unlock(&chop[l]);printf("哲学家 %c 放下了筷子 %d\n",phi,l);pthread_mutex_unlock(&chop[r]);printf("哲学家 %c 放下了筷子 %d\n",phi,r);
*/}
}int main() {for(int i = 0; i < 5 ; i ++) {sem_init(&chop[i],0,1);}pthread_t tid[5];for(int i = 0; i < 5 ; i ++) {pthread_create(&tid[i],NULL,eat_think,&i);}for(int i = 0; i < 5 ; i ++) {sem_destroy(&chop[i]);} pthread_join(tid[0],NULL);return 0;
}
进程通信
- 使用方便,高效地传送大量数据
进程通信类型
- 共享存储器系统:
- 基于共享数据结构的通信方式:适用于传递相对少量的数据,效率低下,低级通信
- 共享存储区的通信方式:高级通信,需要通信的进程在通信前,前向系统申请获得功能共享存储区的一个分区,并附加到自己的地址空间,便可进行操作,不再需要时将其归还给共享存储区
- 管道通信系统(pipe):
- 互斥:一个进程执行操作,其余进程等待
- 同步:读写进程的等待与唤醒
- 只有确定对方存在才能进行通信
- 消息传递系统:高级通信方式
- 直接通信方式:发送进程利用OS所提供的发送原语,直接把消息发送给目标进程
- 间接通信方式:发送和接收进程,都通过共享中间实体的方式完成进程间的通信
- 客户机-服务器系统:
- 套接字(socket):一个通信标识类型的数据结构,是进程通信/网络通信的基本构件
- 基于文件型:通信双方通过对这个文件的读写实现通信,原理类似于管道
- 基于网络型:非对称方式通信,发送者需要提供接收者命名,任何进程都可以发送请求,方便进程间通信的建立
- 优势:适用于网络环境中不同计算机间的通信,,每个套接字拥有唯一的套接字标识符,确保了通信双方间逻辑链路的唯一性,便于实现数据传送得到并发服务,,隐藏实现细节,采用统一接口进行处理
- 远程过程调用和远程方法调用:调用RPC(一个通信协议),用于通过网络连接的系统
- 套接字(socket):一个通信标识类型的数据结构,是进程通信/网络通信的基本构件
消息传递的实现方式
- 直接消息传递系统:
- 直接通信原语:
- 对称寻址方式:要求发送进程和接收进程都以显示方式提供对方的标识符,不是用于实现进程定义的模块化
- 非对称寻址方式:接收进程可能需要与多个发送进程通信,不需要命名发送进程
- 消息的格式:采用变长的消息格式,方便了用户
- 进程的同步方式:发送进程阻塞,接收进程阻塞,用于进程之间紧密同步,发送接受无缓冲,发送进程不阻塞,接收进程阻塞,发送进程和接收进程均不阻塞
- 通信链路:
- 单向通路:只允许发送进程或接收进程其中一方发送信息
- 双向通路:允许进程A与B互相发送信息
- 直接通信原语:
- 信箱通信:属于间接通信方式
- 信箱的结构:信箱头+信箱体
- 信箱通信原语:用于邮箱的创建和撤销,消息的发送和接收
- 信箱的类型:
- 私用信箱:可采用单向通信来实现,拥有此的进程结束,邮箱也消失
- 公用邮箱:双向通信链路实现,一半在系统运行期间始终存在
- 共享邮箱:拥有者和共享者都有权取走或发送自己的消息
- 关系:一对一,一对多,多对一,多对多
线程的基本概念
线程的引入
- 为了减少程序在并发执行时所付出的时空开销,更好的并发性
- 基本属性:
- 一个可拥有资源的独立空间
- 一个可独立调度和分派的基本单位
- 程序并发执行时所需付出的时空开销:
- 创建进程
- 撤销进程
- 进程切换
- 作为调度和分派的基本单位:拥有资源基本单位,,又不实施频繁的切换
线程与进程的比较
\ | 进程 | 线程 |
调度的基本单位 | 传统OS中,作为调度与分派的基本单位,开销较大 | 引入后为调度与分派的基本单位,线程切换不引起进程切换(同一进程中) |
并发性 | 允许进程间并发执行,并发性一般 | 允许进程间并发执行,允许一个进程多个线程并发,并发性更好 |
拥有资源 | 可以拥有资源,并作为系统中拥有资源的一个基本单位源 | 拥有自己少量资源外,允许多个线程共享该进程所拥有资源 |
独立性 | 拥有一个独立的地址空间和其他资源,独立性更好 | 共享进程的内存地址空间和资源,独立性一般 |
系统开销 | 创建/撤销进程,OS付出的开销大于线程创建/撤销所付开销 | |
多处理机 | 只能运行在一个处理机上 | 一个进程多线程分配到多个处理机上,使并发执行加速完成 |
线程的状态和线程控制块
- 三个状态:
- 执行状态
- 就绪状态
- 阻塞状态
- 线程控制块TCB:
- 线程标识符
- 一组寄存器
- 线程运行状态
- 线程专有存储区
- 优先级
- 信号屏蔽
- 堆栈指针
- 多线程OS中进程属性:
- 进程是一个可拥有资源的基本单位
- 多个线程可并发执行
- 进程已不是可执行的实体
线程的实现
线程的实现方式
- 内核支持线程KST:
- 优势:
- 多处理系统中,内核能同时调度同一进程中的多个线程并发执行
- 进程中一个线程被阻塞,可运行其他进程中线程
- 切换快,开销小
- 提高系统执行速度和效率
- 劣势:
- 模式切换开销大
- 优势:
- 用户级线程ULT:
- 优势:
- 线程切换不需要转换到内核空间
- 调度算法可以是进程专用的
- 实现与OS平台无关,可以在不支持多线程机制的操作系统上实现
- 劣势:
- 系统调用的阻塞问题
- 进程中仅有一个线程能执行,其他线程只能等待
- 优势:
- 组合方式:
- 多对一:多个用户线程映射到一个内核控制线程
- 优势:开销小,效率高
- 劣势:一个线程阻塞,整个进程阻塞
- 一对一
- 优势:更好的并发性,一个线程阻塞,允许调度另一个线程执行
- 劣势:开销大
- 多对多:结合上述两种方式的优势
- 多对一:多个用户线程映射到一个内核控制线程
线程的实现
- 内核支持线程的实现与进程的调度与切换相似
- 用户级线程的实现:不能利用系统调用,分为运行时程序和内核控制线程
线程的创建与终止
- 由创建而产生,由调度而执行,由终止而消亡
- 线程的创建:“初始化线程”在创建时,利用一个线程创建函数,提供相应的参数,返回一个线程标识符供以后使用
- 线程的终止:完成任务或出现异常而强行结束,用终止线程通过调用相应函数(或系统调用)对他执行终止操作
本章练习
在单处理机系统中,处于运行状态的进程(A)。
A.最多只有一个
B.可以有多个
C.不能被挂起
D.必须在执行完后才能被撤下原语是(B)
A.一条机器指令
B.若干条机器指令组成,不可被中断
C.一条特定指令
D.中途能打断的指令支持多道程序设计的操作系统在运行过程中,不断地选择新进程运行来实现CPU的共享,但其中(D) 不是引起操作系统选择新进程的直接原因
A.运行进程的时间片用完
B.运行进程出错
C.运行进程要等待某—事件的发生
D.有新进程进入就绪状态在支持多线程的系统中,进程P创建的若干个线程不能共享的是(D)
A.进程P的代码段
B.进程P中打开的文件
C.进程P的全局变量
D.进程P中某线程的栈指针在创建进程时,(A))不是创建进程所必须的步骤
A.由调度程序为进程分配CPU
B.建立一个PCB
C.为进程分配内存
D.将进程插入就绪队列线程控制块TCB中不应拥有的内容是(A)
A.内存地址空间
B.指令计数器PC
C.用户栈指针
D.线程状态我们把在一段时间内,只允许一个进程访问的资源,称为临界资源,因此,我们可以得出下列论述,正确的论述为(D)
A.对临界资源是不能实现资源共享的。
B.只要能使程序并发执行,这些并发执行的程序便可对临界资源实现共享。
C.为临界资源配上相应的设备控制块后,便能被共享。
D.对临界资源,应采取互斥访问方式,来实现共享。有两个并发执行的进程 P1 和 P2,共享初值为 1 的变量 x。P1 对 x 加 1,P2 对 x 减 1,两个操作完成后,x 的值A.可能为©
A.可能为-1 或 3
B.只能为 1
C.可能为 0、1 或 2
D.可能为-1、0、1 或 2关于wait()和signal()操作,下面哪个说法是对的(A)
A.wait()申请一个资源,资源不够,则阻塞,signal()操作释放一个资源,若有进程等待在唤醒
B.wait()申请一个资源,signal()操作释放一个资源,若有进程等待在唤醒
C.wait()申请一个资源,资源不够,则阻塞,signal()操作释放一个资源
D.wait()申请一个资源,signal()操作释放一个资源关于读者和写者问题,下列说法错误的是(D)A.如果有一个读者在读,其他读者也可以读,因此读者与读者之间不需要互斥。
B.如果有一个读者读,其他写者就不能写,因此,读者和写者之间需要互斥。
C.只要有一个写者写,其他写者就不能写,因此,写者和写者之间需要互斥。
D.如果有多个读者,需要设一个共享变量来计数,这个共享变量是临界区关于生产者消费者问题,下列叙述错误的是(E)
A.当只有一个生产者和一个消费者一个缓冲区时,他们之间只有同步关系,不需要互斥信号量。
B.当有多个生产者多个消费者多个缓冲区时,因为可能存在对某个缓冲区的竞争访问,既需要同步也需要互斥。
C.生产者消费者问题可以通过AND信号量解决
D.进程同步关键在于什么时候停(缓冲区满,生产者要停;缓存区空,消费者停),什么时候走(发信号让消费者走;发信号让生产者走)。
E.同步就是把临界区放在wait()和signal()之间,而互斥就是把wait()和signal()分别放在生产者和消费者两个进程/线程中。关于哲学家就餐问题,下列叙述错误的是(D)
A.5个叉子表示5个临界资源,因此设5个信号量,每个的初值为1
B.每个哲学家必须获得两个资源才能吃通心粉,因此可以通过AND信号量解决同步问题。
C.当每个哲学家拿到一个叉子时,谁也吃不到通心粉,这就出现死锁了。
D.用AND信号量信号量解决哲学家就餐问题,依然会出现死锁。
E.当5个哲学家线程同时运行时,可能出现死锁,也可能不出现死锁关于消息通信,以下说法错误的是©
A.当发送消息时,发送原语就陷入到内核态,然后申请消息缓冲区,消息被从用户态缓冲区拷贝到这个消息缓冲区中,接着要找到接收者进程的pcb,并将消息挂到该PCB消息队列队列的末尾。
B.因为消息队列是临界资源,因此插入操作要用wait-signal的操作,确保它们互斥的进行。
C.消息队列是临界资源,应该对其进行互斥的访问,不存在同步问题。D.当发送者进程把消息放到消息队列离开的时,要进行一个signal操作,就是要唤醒接收者进程,告诉接收者进程,队列中有消息了。一般管道通信用于父子进程之间 ,当我们通过fork( )创建了父子进程,那么父子进程的管道都有两个文件描述符,必须关闭其中的一个读端和一个写端,建立一条“父进程写入子进程读取”的通道,或者“子进程写入父进程读取”的通道,这种说法(A)
A.对
B.错管道通信有读端和写端,这个模型是(B)
A.读者-写者
B.生产者消费者
C.哲学家就餐
D.爸爸妈妈儿子女儿吃苹果和橘子
【操作系统】第二章--进程的描述与控制--笔记与理解(2)相关推荐
- 模拟进程创建、终止、阻塞、唤醒原语_操作系统第二章--进程的描述与控制
操作系统第二章--进程的描述与控制 前趋图和程序执行 前趋图 前趋图是一个有向无循环图DAG,用来描述进程之间执行的前后关系 初始结点:没有前趋的结点 终止结点:没有后继的结点 重量:表示该结点所含有 ...
- 操作系统 第二章 进程的描述与控制(4)进程同步(重点)
计算机操作系统 读书笔记 第二章 进程的描述与控制 进程同步(重点) 计算机操作系统 前言 进程同步 一.进程同步的基本概念 1.1 两种形式的制约关系 1.2 临界资源(Critical Resou ...
- 操作系统第二章进程的描述与控制
第二章进程的描述与控制 前驱图和程序执行 程序并发执行 程序的并发执行 程序并发执行时的特征 间断性 失去封闭性 不可再现性 进程的描述 进程的定义 进程是程序的一次执行 进程是一个程序及其数据在处理 ...
- 操作系统第二章-进程的描述与控制
前趋图和程序执行 1.前趋图 所谓前趋图(PG),是指一个有向无环图,可记为DAG,用于描述进程之间执行的先后顺序,具体见书上P35. 没有前趋的结点称为初始结点,没有后继的结点称为终止结点,每个结点 ...
- 【操作系统】 第二章 进程的描述与控制
第二章 进程的描述与控制 2.1 什么是进程 程序代码+相关数据+程序控制块PCB 当处理器开始执行一个程序的代码时,称这个执行的实体为进程 2.1.1 进程和进程控制块PCB PCB(Process ...
- 【操作系统】第二章-进程的描述与控制
第二章.进程的描述与控制 前言 在传统的操作系统中,为了提高资源利用率和系统吞吐量,通常采用多道程序技术,将多个程序同时装入内存,并使之并发运行,传统意义上的程序不再能独立运行.此时,作为资源分配和独 ...
- 考研OR工作----计算机操作系统简答题及疑难知识点总结(第二章 进程的描述与控制)
计算机操作系统从第二章开始内容会变得异常多,还是希望能够帮助到大家,在这一章阿婆主还会把书上的典型的PV操作题给打上来,给大家用作参考,如果有问题的地方,还请大家在文章下方留言,我好更正,或者你们有更 ...
- 第二章 进程的描述与控制
一.名词解释 1.进程上下文 进程执行活动全过程的静态描述. 包括计算机中与执行该进程有关的各寄存器的值.程序段在经过编译之后形成的机器指令代码集(正文段).数据集.各种堆栈和PCB结构. * 进程控 ...
- 操作系统学习笔记——第二章 进程的描述与控制(二)
2.3 进程控制 进程控制是对系统中的全部进程实施有效的管理,包括进程创建.终止.进程阻塞和唤醒. 一.进程的创建 二.进程的终止 三.进程的阻塞与唤醒 四.进程的挂起与激活 一.进程的创建 创建原语 ...
最新文章
- iOS中UITextField 使用全面解析
- mysql 行自动增量为23,Mysql Innodb:自动增量非主键
- python网课推荐 知乎-知乎看了很多推荐,最终选了这本Python入门
- Android多线程之同步锁的使用
- 近找到了一个免费的python教程,两周学会了python开发【内附学习视频】
- Bella Protocol已按计划调整流动性挖矿奖励方案
- NFC的实用性有多高,真的是刚需吗?
- 电脑如何安装php文件夹在哪个文件夹,win7系统桌面文件在c盘哪个文件夹
- 产品经理学习一(定义、分类、成员配合、调研、3D文档、竞品分析、SWOT分析)
- OEM造就整个IT产业
- Qt 将中文汉字转成拼音与简拼
- [C#] http如何在POST之后下载文件
- orc识别较慢_提高OCR识别效率的诀窍
- Android Native层
- 半次元cos图片爬虫
- windows录屏_Windows及苹果电脑录屏攻略
- 第一课:什么是树莓派
- hbase+dataframe+java_Java实现Spark将DataFrame写入到HBase
- 《算法竞赛入门经典(第二版)》习题解答——第二章
- c# dotNetCore 使用 Session
热门文章
- MySQL之父开发的 MariaDB 数据库,扩展了新功能……
- 名帖385 文徵明 行草《雪诗卷》
- 你喜欢天长地久,还是曾经拥有?
- 渗透学python的哪方面_渗组词_渗字组词
- Pycharm2018永久破解方法
- Android微信登录引起的内存泄漏
- 另类办公Word2003也当“扫描大师”(转)
- 女生玩游戏什么款式蓝牙耳机好用?小清新高颜值游戏蓝牙耳机推荐
- 关于Bellman-Ford算法的个人理解
- 2.牛批了 Android 2021中高级面试题 一线大厂和二线大厂面试真题精选 (京东 附答案)第二套 22k+