多进程与多线程

进程和线程

1. 进程和线程的概念

进程: 进程是资源分配的最小单位, 它是程序执行时的一个实例, 在程序运行时创建

线程: 线程是程序执行的最小单位, 是处理器调度的基本单位, 是进程的一个执行路径, 一个进程有多个线程组成

2. 线程和进程有什么区别

  1. 概念上的区别
  2. 进程有自己的独立地址空间, 每启动一个进程, 系统就会为它分配地址空间, 简历数据表来维护代码段, 堆栈段和数据段, 这样的代价是非常昂贵的; 线程是共享进程中的数据, 使用相同的地址空间, 因此, CPU切换一个线程的花费远比进程小很多, 同时创建一个线程的开销也比进程小很多;
  3. 线程之间的通信更方便, 同一进程下的线程共享全局变量, 静态变量等数据, 而**进程之间的通信需要以通信的方式(IPC) **进行; 多进程的程序要更健壮一些, 多线程程序一个线程死掉, 整个进程也跟着死掉, 而一个进程死掉并不会对另外一个进程造成影响, 因为进程是有自己独立的地址空间;
  4. 进程切换时, 消耗的资源大, 效率低; 所以涉及到频繁的切换时, 使用线程要好于进程; 同样如果要求同时进行并且又要共享某些变量的并发操作, 只能用线程不能用进程;
  5. 执行过程不一样: 每个独立的进程有一个程序运行的入口, 顺序执行序列和程序入口; 但是线程不能独立执行, 必须依存在应用程序中, 由应用程序提供多个线程执行控制;
  6. 线程执行开销小, 但是不利于资源的管理和保护; 线程适合在双CPU系统上运行; 进程执行开销大, 但是能很好的进行资源管理和保护;

3. 什么时候使用多进程, 什么时候使用多线程

对资源的管理和保护要求高, 不限制开销和效率的时候, 使用多进程;

要求效率高, 频繁切换时, 资源的保护管理要求不是很高时, 使用多线程;

4.进程五态

  1. 分为哪五态: 创建态, 就绪态, 运行态, 阻塞态, 终止态;

    创建态: 一个应用程序从系统上启动, 首先进入的就是创建态, 需要获取系统资源创建进程管理快(PCB) 完成资源分配

    就绪态: 在创建态完成之后, 进程已经准备好了, 但是还没有活得到处理器资源, 此时无法运行

    运行态: 获取处理器资源, 被系统调用, 开始进入运行态; 如果进程的时间片用完了就变成就绪态

    阻塞态: 在运行态期间, 如果进行了阻塞的操作, 比如耗时的I/O操作, 这个时候进程暂时无法操作就进入到了阻塞态, 等待I/O操作完成后就进入就绪态

    终止态: 进程结束或系统终止, 进入终止态

5. 创建进程有哪些方式(了解即可)

  1. 系统初始化 (比如查看进程 linux 中用ps命令, 前台进程负责于用户交互, 后台运行的进程与用户无关, 运行在后台并且只在需要时才唤醒的进程, 成为守护进程, 如: qq打开个邮箱, web页面等等)
  2. 一个进程在运行过程中开启了子进程 (如 nginx开启多进程, fork, popen等等)
  3. 用户的交互式请求, 而创建一个新进程 (如用户用鼠标双击点开任意软件图片等等)
  4. 一个批处理作业的初始化 (只在大型机的批处理系统中应用)

无论是哪一种, 新进程的创建都是由一个已经存在的进程执行了一个用于创建进程的系统调用而创建的;

6. 进程间通信(重点!!!)

在Linux下的多个进程间的通信机制叫做IPC(Inter-Process Communication), 它时一个多个进程之间相互通过的一种方法; 进程间通信有七种: **管道(PIPE), 命名管道(FIFO), 信号(signal), 信号量(Semaphore), socket, 消息队列(Message queues), 共享内存(Share Memory) **; 要知道一点, 共享内存是最快的;

1. 管道:

定义:

​ 管道实际上是用于进程间通信的一段共享内存, 创建管道的进程叫做管道服务器, 连接到一个管道的进程叫做管道客户机; 一个进程在向管道写入数据后, 另一个进程就有从管道的另一端读取出来;

特点:

​ 管道是半双工的, 数据只能向一个方向流动; 需要双方通信的时候, 需要建立俩个管道;

​ 管道只能用于父子进程或者兄弟进程之间(具有亲缘关系的进程); 比如fork或exec创建的新进程;

​ 单独构成一种独立的文件系统: 管道对于管道俩端的进程而言, 就是一个文件, 但是它不是普通的文件, 不属于某种文件系统, 是一个单独构成的文件系统, 并且只能存在在内存中;

​ 数据的读出和写入: 一个进程向管道中写的内容被另一端的进程读出; 写入的内容每次都添加到管道缓冲区的末尾, 并且每次都是从缓冲区的头部读出数据; 这其实就是一个队列, 先进先出;

实现:

管道是由内核管理的一个缓冲区, 它的实现是一个循环buff, 是一个环形缓冲区, 当管道中没有消息的话, 从管道中读取消息的进程会进入等待; 消息满了的时候, 放入消息的进程就会等待, 直到另一端的进程放入信息; 管道只能在本地计算机中使用, 不可以用来网络间的通信; 它的底层是使用read和write调用来进行读和写数据;

函数原型及使用:

#include <unistd.h> 
int pipe(int file_descriptor[2]);
// 使用用例
int fd[2]
int result = pipe(fd);

2. 命名管道

定义:

​ 命名管道是一个特殊类型的文件, 它在系统中以文件形式存在; 这样就克服了没有亲缘关系的进程间通信;

函数原型及调用:

#include <sys/types.h> 
#include <sys/stat.h> 
int mkfifo(const char *filename,mode_t mode); //建立一个名字为filename的命名管道, 参数mode为该文件的权限, 若成功则返回0, 否则返回-1, 错误原因存于errno中.
// 使用用例
mkfifo( "/tmp/cmd_pipe", S_IFIFO | 0666 );
// 创建这个命名管道之后, 就可以直接使用open, read, write等系统调用

管道和命名管道的区别

​ 对于命名管道来说, IO操作和普通管道IO操作基本一样, 但是俩者有一个主要区别, 在管道中, 这个管道是已经创建好的; 创建命名管道之后, 必须使用open函数来显示地建立到管道的通信, 而在管道中, 管道已经在主进程中创建好了, 然后fork时直接复制相关数据或exec创建的新进程时把管道的文件描述符当参数传递进去;

3. 信号

信号机制是unix系统中最为古老的进程之间通信机制, 用于一个或几个进程之间传递异步信号;

函数原型及使用:

#include <sys/types.h>
#include <signal.h>
void (*signal)(int sig, void (*func)(int)))(int);
// 使用用例
int ret = signal(SIGSTOP, sig_handle);
signalaction()

4. 信号量

定义:

​ 信号量是一种计数器, 用于控制多个线程共享的资源进行的访问; 它经常被用作一个锁机制, 在某个进程正在对特定的资源进行操作时, 信号量可以防止另一个进程去访问;

​ 信号量是特殊的变量, 它只取正整数并且只允许对这个值进行俩个操作: 申请和释放(P申请和V操作);

​ P(sv): 如果sv的值大于0, 就给它 - 1; 如果它的值等于0, 就挂起该进程的执行;

​ V(sv): 如果有其他进程因等待sv而被挂起, 就让他恢复运行; 如果没有其他进程因等待sv而挂起, 则给它+1;

函数原型及调用:

#include <sys/types.h> 
#include <sys/stat.h>
#include <sys/sem.h>

int semget(key_t key, int num_sems, int sem_flags); //semget函数用于创建一个新的信号量集合 , 或者访问一个现有的集合(不同进程只要key值相同即可访问同一信号量集合)。第一个参数key是ftok生成的键值,第二个参数num_sems可以指定在新的集合应该创建的信号量的数目,第三个参数sem_flags是打开信号量的方式。
// 使用案例
int semid = semget(key, 0, IPC_CREATE | IPC_EXCL | 0666);//第三个参数参考消息队列int msgget(key_t key,int msgflag);第二个参数。
int semop(int sem_id, struct sembuf *sem_ops, size_t num_sem_ops); //semop函数用于改变信号量的值。第二个参数是要在信号集合上执行操作的一个数组,第三个参数是该数组操作的个数 。
struct sembuf sops = {0, +1, IPC_NOWAIT};//对索引值为0的信号量加一。
semop(semid, &sops, 1);//以上功能执行的次数为一次。
int semctl(int sem_id, int sem_num, int command,...); //semctl函数用于信号量集合执行控制操作,初始化信号量的值,删除一个信号量等。 类似于调用msgctl(), msgctl()是用于消息队列上的操作。第一个参数是指定的信号量集合(semget的返回值),第二个参数是要执行操作的信号量在集合中的索引值(例如集合中第一个信号量下标为0),第三个command参数代表要在集合上执行的命令。
IPC_STAT:获取某个集合的semid_ds结构,并把它存储到semun联合体的buf参数指向的地址。
IPC_SET:将某个集合的semid_ds结构的ipc_perm成员的值。该命令所取的值是从semun联合体的buf参数中取到。
IPC_RMID:内核删除该信号量集合。
GETVAL:返回集合中某个信号量的值。
SETVAL:把集合中单个信号量的值设置成为联合体val成员的值。

5. 命名socket

​ socket也是一种进程间通信机制, 与其他通信机制不同的是, 它可用于不同进程间的进程通信;

​ 命名socket与普通的TCP/IP网络 socket相比具有以下特点:

1. UNIX域套接字域传统套接字的区别是用路径名表示协议族的描述,ls -l看到的该文件类型为 s  
2. UNIX域套接字TCP套接字相比, 在同一台主机的传输速度前者是后者的俩倍; UNIX域套接字仅仅复制数据, 并不执行协议处理, 不需要添加或删除网络报头, 无需计算校验和, 不产生顺序号, 也不需要发送确认报文
3. UNIX域套接字可以在同一台主机上各进程之间传递文件描述符

​ 命名socket与TCP/IP网络socket通信使用的是同一套接口, 只是地址结构与某些参数不同: TCP/IP网络socket通过IP地址和端口号来标识, 而UNIX域协议中使用普通文件系统路径名标识. 所以命名socket和普通socket只是在创建socket和绑定服务器标识的时候与网络socket有区别, 具体如:

int socket(int domain, int type, int protocol);
说明:创建一个socket, 可以是TCP/IP网络socket, 也是命名socket, 具体由domain参数决定. 该函数的返回值为生成的套接字描述符.
参数domain指定协议族: 对于命名socket/Unix域socket, 其值须被置为 AF_UNIX或AF_LOCAL; 如果是网络socket则其值应该为 AF_INET; 
参数type指定套接字类型, 它可以被设置为 SOCK_STREAM(流式套接字)或 SOCK_DGRAM(数据报式套接字); 
参数protocol应被设置为 0;   

​ 特别注意一下: 命名socket命名方式有两种。一是普通的命名,socket会根据此命名创建一个同名的socket文件,客户端连接的时候通过读取该socket文件连接到socket服务端。这种方式的弊端是服务端必须对socket文件的路径具备写权限,客户端必须知道socket文件路径,且必须对该路径有读权限。另外一种命名方式是抽象命名空间,这种方式不需要创建socket文件,只需要命名一个全局名字,即可让客户端根据此名字进行连接。后者的实现过程与前者的差别是,后者在对地址结构成员sun_path数组赋值的时候,必须把第一个字节置0,即sun_path[0] = 0。这里第一种方式比较常见,下面的例程中我们就使用了该方法。

6. 消息队列

定义:

​ 提供了一个从一个进程向另外一个进程发送一块数据的方法, 每个数据块都被认为是有一个类型, 接收者进程接受的数据可以有不同的类型值; 消息队列也有管道的一样不足, 就是每个消息会有最大长度上限(MSGMAX), 每个消息队列的总的字节数是有上限的(MSGMNB), 系统上消息队列的总数也有一个上限(MSGMNI);

实现:

​ 消息队列是内核地址空间的内部链表, 通过Linux内核维护; 系统中可能有很多的消息队列, 每个消息队列用消息队列描述符(消息队列ID: qid)来区分, qid是唯一的, 用来区分不同的消息队列; 以下是消息队列的模型:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hVvpRiRm-1630857241526)(C:\Users\小黑\AppData\Roaming\Typora\typora-user-images\image-20210831131102094.png)]

消息队列使用到的函数

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

key_t ftok(const char *pathname, int proj_id);

/******************************************************************
* 函数说明: 用来创建消息队列ID, 成功返回一个非负整数, 即该共享内存段的标识码
* 参数说明: 
*		第一个参数key是ftok()返回的key_t类型
*		第二个参数msgflg是创建标志; IPC_CREAT(不存在则创建, 存在则返回已有的mqid, IPC_CREAT|IPC_EXCI(不存在则创建, 存在则返回出错)
*******************************************************************/
int msgget(key_t key, int msgflg);

/******************************************************************
* 函数说明: 用来发送一个消息, 必须有写消息队列的权限; 成功返回0, 失败返回-1, 并设置error
* 参数说明: 
*		第一个参数msqid是由msgget函数返回的消息队列ID;
*		第二个参数msgp是一个指针, 它指向要发送的消息结构体类型的变量, 消息结构在俩方面受到制约; 首先, 他必须小于系统规定的上限值; 其次, 它必须以一个long int 长整数开始, 接收者函数将利用这个长整数确定消息的类型;
*		第三个参数msgsz是要发送消息的长度
*		第四个参数msgflg控制着当前消息队列满或到达系统上限时将要发送的事情, 设置为IPC_NOWAIT表示队列满不等待, 返回EAGAIN错误;
*******************************************************************/
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

/******************************************************************
* 函数说明: 用来从一个消息队列接受消息; 成功返回实际放到接收缓冲区里去的字符个数, 失败返回-1, 并设置erron;
* 参数说明: 
*		第一个参数msqid是由msgget函数返回的消息队列ID;
*		第二个参数msgp是一个指针, 它指向要发送的消息;
* 		第三个参数msgsz是msgp指向的消息长度, 这个长度不含保存消息类型的那个long int长整型;
* 		第四个参数msgtype是消息的类型类型: 值为0 返回队列的第一条消息; 值大于0 返回队列第一条类型等于msgtype的消息; 值小于0 返回队列第一条类型小于等于msgtype绝对值的消息, 并且是满足条件的信息类型最小的消息;
*		第五个参数msgflg控制着队列中没有相应类型的消息可供接受时将要发送的事; 值为 IPC_NOWAIT, 队列没有可读消息不等待, 返回ENOMSG错误; 值为 MSG_NOERROR, 消息大小可以超过msgsz时被截断; 值为 大于0且为MSG_EXCEPT,接受类型不等于msgtype的第一条消息;
*******************************************************************/
size_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);

/******************************************************************
* 函数说明: 该函数用于控制消息队列
* 参数说明: 
*		第一个参数msqid是由msgget函数返回的消息队列ID;
*		第二个参数cmd是要采取的操作, 它可以取以下的三个值: 
			IPC_STAT: 把msqid_ds结构中的数据设置为消息队列的当前关联值;
			IPC_SET:  如果进程由足够的权限, 就把消息队列的当前关联值设置为msqid_ds结构中给出的值;
			IPC_RMID: 删除消息队列
		第三个参数, buf是一个结构指针, 它指向存储消息队列的相关信息的buf;
*******************************************************************/
int msgctl(int msqid, int cmd, struct msqid_ds *buf);

7. 共享内存

定义:

​ 共享内存, 就是俩个或多个进程都可以访问的同一块内存空间, 一个进程对这块空间内容的修改可为其他参与通信的进程所看到的. 显然, 为了达到这个目的就需要做俩件事: 一件事在内存划出一块区域来作为共享区; 另一件事把这个区域映射到参与通信的各个进程空间. 共享内存事最快的IPC形式. 一旦这样的内存映射到共享它的进程的地址空间, 把这些进程间数据传递不在涉及到内核, 也就是说进程之间不需要执行进入内核的系统调用来传递彼此的数据; 共享内存本身并没有同步机制, 需要程序员自己控制. 下面是一个共享内存图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZyeuwTYB-1630857241531)(C:\Users\小黑\AppData\Roaming\Typora\typora-user-images\image-20210831140526837.png)]

共享内存用到的函数

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>

key_t ftok(const char *pathname, int proj_id);

int shmget(key_t key, size_t size, int shmflg); // 该函数用来创建共享内存, 成功返回一个非负整数, 即该共享内存段的标识码; 失败返回-1
void *shmat(int shmid, const void *shmaddr, int shmflg); // 第一次创建完共享内存时, 它还不能被任何进程访问, shmat函数的作用就是用来启动对该共享内存的访问, 并把共享内存连接到当前的地址空间; 成功返回一个指向共享内存第一字节的指针, 失败返回-1
int shmdt(const void *shmaddr); // 函数用于将共享内存从当前进程中分类; 注意: 共享内存分离并不是删除它, 只是使该共享内存对当前的进程不可再用; 成功返回0, 失败返回-1
int shmctl(int shmid, int cmd, struct shmid_ds *buf); //用来控制共享内存

线程间通信以及同步

线程间通信

  1. 一个进程中的线程与另一个进程中的线程通信, 由于俩个线程只能访问自己专属进程的地址空间和资源, 所以还是进程间通信

  2. 同一个进程中的俩个线程通信

    同一进程的不同线程是共享一份全局内存区域, 其中包含初始化数据端, 未初始化数据端, 以及堆内存段, 所以进程之间可以方便, 快速的共享信息; 只要将数据复制到共享(全局或堆)变量中即可; 要避免多个线程试图同时修改同一份信息;

线程的同步

为什么要有线程同步的概念呢?

​ 所在的线程中有多个进程在同时运行, 而这些进程可能会同时共享某一段代码. 如果说每次运行的结果和单线程运行的结果是一样的, 而且其他的变量的值也和预期是一样的, 就是线程安全的; 线程安全就是说多线程访问同一段代码不会产生不确定的结果; 这个时候就有线程同步来解决这个不安全的问题;

线程同步实现的方式

1. 互斥锁:

互斥量本质上说是一把锁, 一个线程在访问共享资源前对互斥量进行加锁, 在访问完成后释放互斥量. 对互斥量进行加锁之后, 其他的线程再去对互斥量进行加锁会被阻塞直到当前的线程释放互斥锁. 如果释放互斥量时有一个以上的线程阻塞, 其他线程就会看到互斥量依旧时锁着, 只能再次阻塞等待它重新可用, 这样, 一次只能有一个线程在执行.

#include <pthread.h>

int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr);//互斥初始化
int pthread_mutex_destroy(pthread_mutex_t *mutex);//销毁互斥
int pthread_mutex_lock(pthread_mutex_t *mutex);//锁定互斥
int pthread_mutex_unlock(pthread_mutex_t *mutex);//解锁互斥
int pthread_mutex_trylock(pthread_mutex_t *mutex);//销毁互斥
eg.pthread_t mutex;
pthread_mutex_init(&mutex, NULL);
pthread_mutex_lock(&mutex);
...
pthread_mutex_unlock(&mutex);
pthread_mutex_detroy(&mutex);

要注意互斥量的死锁:

一个线程需要访问俩个或更多不同的共享资源, 而每个资源又有不同的互斥量管理. 当超过一个线程加锁同一组互斥量时, 就可能发生死锁.

死锁: 就是多个线程/进程因竞争资源而造成的一种僵局(相互等待)

死锁的处理策略:

​ 1. 预防死锁: 破坏死锁产生的四个条件: 互斥变量, 不剥夺条件, 请求和保持条件以及循环等待

  1. 避免死锁: 在每次进行资源分配前, 应该计算此次分配资源的安全性, 如果资源分配不会导致系统进入不安全的状态, 那么将资源分配给进程, 否则等待; 这里最出名的就是银行家算法
  2. 检测死锁: 检测到死锁后通过资源剥夺, 撤销进程, 进程回退等方法解除死锁

2. 自旋锁

自旋锁和互斥量是类似的, 但是它不是通过使休眠使进程阻塞, 而是在获取锁之前一直处于忙等(自选)阻塞状态. (这里注意一点: 他还是在运行态, 并不是真正意义上的阻塞);

什么情况下可用自旋锁: 比如锁被持有的时间短, 而且线程不需要再重新调度上花费太多的成本

3. 读写锁

读写锁和互斥量类似, 不过读写锁拥有更高的并行性. 互斥量要么是不加锁状态, 而且一次只有一个线程可用对其加锁. 读写锁有3种状态: 读模式下加锁状态, 写模式下加锁状态, 不加锁状态. 一次只有一个线程可用占有写模式的读写锁, 但是多个线程可用同时占有读模式的读写锁.

当读写锁是写加锁状态时, 再这个锁被解锁之前, 所有对这个锁加锁的线程都会阻塞; 当加的是读锁的时候, 所有以读模式加锁的线程都可以得到访问权, 但是任何希望以写模式加锁的线程都会阻塞, 直到线程释放他们的读锁为止;

#include <pthread.h>

int pthread_rwlock_init(pthread_rwlock_t *rwlock, const pthread_rwlockattr_t *rwlockattr);//初始化读写锁
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);//销毁读写锁
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);//读模式锁定读写锁
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);//写模式锁定读写锁
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);//解锁读写锁
eg.pthread_rwlock_t q_lock;
pthread_rwlock_init(&q_lock, NULL);
pthread_rwlock_rdlock(&q_lock);
...
pthread_rwlock_unlock(&q_lock);
pthread_rwlock_detroy(&q_lock);

4. 条件变量

条件变量是线程可用的另一种同步机制. 互斥量用于上锁, 条件变量用于等待, 并且条件变量总是需要与互斥量一起使用, 运行线程以无竞争的方式等待特定的条件发生.

条件变量本身是由互斥量保护的, 线程再变量条件变量之前首先要锁住条件变量. 其他线程再获取互斥量之前不会察觉到这种变化, 因为互斥量必须锁定之后才能计算条件.

#include <pthread.h>

int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);//初始化条件变量
int pthread_cond_destroy(pthread_cond_t *cond);//销毁条件变量
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);//无条件等待条件变量变为真
int pthread_cond_timewait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *tsptr);//在给定时间内,等待条件变量变为真
eg.pthread_mutex_t mutex;
pthread_cond_t cond;
...
pthread_mutex_lock(&mutex);
pthread_cond_wait(&cond, &mutex);
...
pthread_mutex_unlock(&mutex);
...

5. 信号量

线程的信号量和进程的信号量类似, 使用线程的信号量可以高效地完成基于线程的资源计数. 信号量实际是一个非负的整数计数器, 用来实现对公共资源的控制. 信号量的值大于0时, 才能访问信号量所代表的公共资源.

#include <semaphore.h>

sem_t sem_event;
int sem_init(sem_t *sem, int pshared, unsigned int value);//初始化一个信号量 
int sem_destroy(sem_t * sem);//销毁信号量
int sem_post(sem_t * sem);//信号量增加1
int sem_wait(sem_t * sem);//信号量减少1
int sem_getvalue(sem_t * sem, int * sval);//获取当前信号量的值

同步和互斥的区别:

互斥:是指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的。

同步:主要是流程上的概念,是指在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问。在大多数情况下,同步已经实现了互斥,特别是所有写入资源的情况必定是互斥的。少数情况是指可以允许多个访问者同时访问资源。

互斥锁、条件变量和信号量的区别:
互斥锁:互斥,一个线程占用了某个资源,那么其它的线程就无法访问,直到这个线程解锁,其它线程才可以访问。
条件变量:同步,一个线程完成了某一个动作就通过条件变量发送信号告诉别的线程,别的线程再进行某些动作。条件变量必须和互斥锁配合使用。
信号量:同步,一个线程完成了某一个动作就通过信号量告诉别的线程,别的线程再进行某些动作。而且信号量有一个更加强大的功能,信号量可以用作为资源计数器,把信号量的值初始化为某个资源当前可用的数量,使用一个之后递减,归还一个之后递增。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值