进程同步是操作系统中用于协调多个进程执行顺序的过程,以确保进程能够有效地共享资源并相互合作。进程同步的目的是实现程序的执行可再现性。
互斥锁(Mutex):互斥锁是一种最常见的同步机制,用于保护共享资源,使得同一时间只有一个进程或线程可以访问该资源。当一个进程获取到互斥锁时,其他进程需要等待直到该进程释放锁。
信号量(Semaphore):信号量是一种计数器,用于控制对共享资源的访问。它可以用来实现进程的互斥和同步。信号量有两种类型:二进制信号量和计数信号量。二进制信号量只能取0或1,用于实现互斥。计数信号量可以取多个非负整数值,用于控制资源的数量。
条件变量(Condition Variable):条件变量用于在多个进程或线程之间进行通信和同步。它通常与互斥锁结合使用。进程可以在条件变量上等待某个条件满足,当条件满足时,其他进程可以通过发出信号来唤醒等待的进程。
读写锁(Read-Write Lock):读写锁允许多个进程同时读取共享资源,但只允许一个进程进行写操作。这样可以提高并发性能,因为多个进程可以同时读取资源而不会相互干扰,但写操作需要互斥保护。
事件(Event):事件是一种同步对象,用于在多个进程之间进行通信和同步。一个事件可以处于"已触发"或"未触发"的状态。进程可以等待事件的触发,当事件触发时,等待的进程将被唤醒。
特点:
特点:
特点:
记录型信号量(Record Semaphores)是一种信号量的扩展形式,用于实现更复杂的同步机制。与传统的二进制信号量或计数信号量不同,记录型信号量可以存储更多的信息,并支持更丰富的同步操作。
记录型信号量可以用于实现进程同步的一般方法,包括:
互斥锁(Mutex):记录型信号量可以用作互斥锁,通过设置信号量的值为1或0来表示锁的状态。当一个进程获得互斥锁时,信号量的值为1,其他进程需要等待直到该进程释放锁,将信号量的值设置为0。
条件变量(Condition Variable):记录型信号量可以用作条件变量,用于等待和通知特定的条件。进程可以通过等待记录型信号量的值为特定值来等待条件的满足,而其他进程可以通过修改记录型信号量的值来通知等待的进程条件已满足。
AND信号量(AND Semaphores)是一种多个信号量的组合,其中所有的信号量都需要满足特定条件才能通过。它可以用于实现更为复杂的同步要求。例如,可以使用AND信号量来实现多个资源同时就绪时才允许进程执行的同步操作。
信号量集(Semaphore Sets)是一组关联的信号量,可以进行集合操作。通过信号量集,可以对多个信号量进行统一管理和操作。进程可以对整个信号量集进行等待、通知和修改操作,从而实现更灵活的进程同步。
进程控制的主要功能是对系统中所有进程实施有效的管理,它具有创建新进程,撤销已有进程,实现进程状态转换等功能。
简化理解:就是要实现进程状态转换。
用“原语”实现(原语的执行不可分割)
为什么进程控制需要一气呵成?
如果步骤1,2被中断会导致数据和状态的不同。
eg:完成第一步后收到中断信号,此时state转变但是队列不变,导致信息不匹配。
- 操作系统初始化,建立起一些常驻内存的系统进程
- 使用创建原语,用于创建非常驻内存系统进程和用户进程
创建一个空白PCB,分配一个唯一的进程标识符;
为新进程的程序和数据分配内存空间
初始化进程控制块:初始化标识符信息,填入处理机的状态信息(指令指针,栈指针)和控制信息(状态,优先级…);
设置相应的链接。如:把新进程加到就绪队列的链表中。
主动调用阻塞原语(block)将自己阻塞。
保存CPU现场到PCB中
状态由运行态改为阻塞态
插入到事件阻塞队列中
进程调度程序选择一个就绪程序,修改状态,恢复CPU现场,投入运行。
当被阻塞进程期待事件到来时,调用唤醒原语(wakeup)唤醒进程。
- 触发事件:
硬件中断,其它进程发信息- 阻塞进程从阻塞队列移出;
- 状态由阻塞态改为就绪态
- 插入到就绪队列中
进程将自己挂起或父进程将子进程挂起时,调用挂起原语(suspend)挂起
- 活动就绪态-》静止就绪态;
- 活动阻塞态-》静止阻塞态;
- 运行态-》静止就绪。并除法重新调度。
活动就绪态表示进程已经准备好并具备运行的条件,正在等待CPU的执行权。静止就绪态表示进程满足了运行条件,但由于某些原因暂时无法被调度执行,需要等待特定条件满足后才能转为活动就绪态。
- 正常结束:完成时间片后结束。
- 异常结束:发生错误。内存越界,算术运算错等。
- 外界干预:操作系统干预,父进程请求,父进程中止。
进程中止时机
将进程的状态由挂起态改为就绪态。
将进程插入到就绪队列中,参与调度。
线程是一个基本的CPU执行单元,也是程序执行流的最小单位。
线程的特征包括:
轻量性:相比于进程,线程的创建、切换和销毁的开销更小,因为线程共享进程的资源。线程的创建和切换比进程快速,因为它们不需要分配新的内存空间。
共享性:线程可以访问和共享进程的资源,包括内存、文件等。多个线程可以在同一个进程中并发执行,共同完成任务。
并发性:多个线程可以同时执行,实现并发处理。线程之间可以并行执行,提高系统的资源利用率和响应性。
独立性:线程拥有自己的程序计数器、栈和局部变量等,每个线程都以独立的方式执行任务。线程之间可以独立调度、同步和通信。
可并行性:多个线程可以在多核处理器上并行执行,充分利用多核计算资源。
共享内存:线程之间共享进程的内存空间,可以通过共享内存进行高效的数据交换和通信。
上下文切换:线程之间的切换需要保存和恢复上下文信息,包括程序计数器、寄存器等。
TCB(Thread Control Block)是线程控制块的缩写,也称为线程控制结构或线程描述符。它是操作系统用来管理和控制线程的数据结构,用于存储和维护线程的状态和相关信息。
TCB包含了以下成员:
TCB是操作系统用于管理线程的重要数据结构,它存储了线程的关键信息,包括状态、寄存器值、资源和调度信息等。通过操作TCB,操作系统可以实现线程的创建、销毁、调度和同步等操作,保证线程的正确执行和协调。
有的进程需要”同时“做许多事情,但是传统的进程只能串行地执行一系列程序。为此,引入”线程“来增加并发度
多对一模型将多个用户级线程映射到一个内核级线程. 线程管理由用户空间的线程库完成,当一个线程调用系统调用陷入内核,整个进程将被阻塞,一次只有一个线程可以访问内核.因此多个线程无法在多处理器上并行运行.
此模型提供更多的并发性,当一个线程陷入内核时其它线程还可以运行,也就是说多个线程可以在微处理器下并行执行
缺点:创建用户线程时需要相应的内核线程
用户级线程和内核级线程是两种不同的线程实现方式,用于管理和调度线程的执行。
选择用户级线程还是内核级线程取决于应用的需求和目标。用户级线程适用于对并发性要求不高、对调度和同步有自定义需求的应用;而内核级线程适用于需要充分利用多核处理器、依赖于操作系统调度和资源管理的应用。在实际应用中,也存在混合线程模型,以充分利用用户级线程和内核级线程的优势。
#include
#include
// 线程函数
void* thread_function(void* arg) {
int thread_arg = *(int*)arg;
printf("Thread argument: %d\n", thread_arg);
printf("Hello from the thread!\n");
// 获取当前线程的线程ID
pthread_t thread_id = pthread_self();
// 获取线程TCB指针
pthread_key_t tcb_key;
void* tcb_ptr = NULL;
pthread_key_create(&tcb_key, NULL);
tcb_ptr = pthread_getspecific(tcb_key);
printf("Thread ID: %lu\n", (unsigned long)thread_id);
printf("Thread TCB: %p\n", tcb_ptr);
pthread_exit(NULL);
}
int main() {
pthread_t thread;
int arg = 123;
// 创建线程
int result = pthread_create(&thread, NULL, thread_function, &arg);
if (result != 0) {
perror("Thread creation failed");
return 1;
}
// 等待线程结束
result = pthread_join(thread, NULL);
if (result != 0) {
perror("Thread join failed");
return 1;
}
printf("Thread joined\n");
return 0;
}
进程间通信(Inter-Process Communication,IPC)是指两个进程之间产生数据交互。
原理:各个进程原本只能访问属于自己的地址空间,但是对于想要进行通信的进程可以开辟一个单独的共享存储区进行进程通信。当多个进程进行通信,为了防止写覆盖,进程间对于共享空间的访问应当互斥进行。
LINUX中实现方式
int shm_open(...); // 想要进行通信的进程通过方法调用,申请一片共享内存区
void * mmap(...); //双方程序都调用mmap方法,将通向内存区映射到进程自己的地址空间
进程间的数据交换以格式化的信息为单位。进程通过操作系统提供的“发送/接受信息”两个原语进行数据交换。
信息传递分为直接通信方式和间接通信方式
直接通信方式
过程:进程P要给进程Q发送一个信息,首先通过发送原语,send(Q,msg)指定目标进程和信息,然后这个信息会被复制到进程Q的进程控制(PCB)的消息队列上,然后进程Q通过接收原语,receive(P,&msg)将信息复制到进程Q的地址空间上。
指明进程发送
间接通信方式
过程:进程p首先将信息放到自己的地址空间完善信息,然后通过send原语将信息发送到A信箱,然后q进程通过receive原语将信息从A取出放到自己地址空间。
- 管道只能采用半双工通信,如果要双向同时通信则需要设置两个管道。
- 各个管道要互斥的访问管道。
- 当管道写满时,写进程将被阻塞,直到读进程取走数据,才可以唤醒。
- 当管道读空时,读进程将被阻塞,直到写进程写入数据,才可以唤醒。
#include
#include
#include
#include
#include
#include
#define MAX_DATA_LEN 256
#define DELAY_TIME 1
int main()
{
pid_t pid;
int pipe_fd[2];
char buf[MAX_DATA_LEN];
const char data[] = "Pipe Test Program";
int real_read, real_write;
memset((void*)buf, 0, sizeof(buf));
/* 创建管道 */
if (pipe(pipe_fd) < 0)
{
printf("pipe create error\n");
exit(1);
}
if ((pid = fork()) == 0)
{
/* 子进程关闭写描述符,并通过使子进程暂停3s等待父进程已关闭相应的读描述符 */
close(pipe_fd[1]);
sleep(DELAY_TIME * 3);
/* 子进程读取管道内容 */
if ((real_read = read(pipe_fd[0], buf, MAX_DATA_LEN)) > 0)
{
printf("%d bytes read from the pipe is '%s'\n", real_read, buf);
}
/* 关闭子进程读描述符 */
close(pipe_fd[0]);
exit(0);
}
else if (pid > 0)
{
/* 父进程关闭读描述符,并通过使父进程暂停1s等待子进程已关闭相应的写描述符 */
close(pipe_fd[0]);
sleep(DELAY_TIME);
if((real_write = write(pipe_fd[1], data, strlen(data))) != -1)
{
printf("Parent wrote %d bytes : '%s'\n", real_write, data);
}
close(pipe_fd[1]); /*关闭父进程写描述符*/
waitpid(pid, NULL, 0); /*收集子进程退出信息*/
exit(0);
}
return 0;
}
复习信号量、临界区、临界资源、直接制约和间接制约关系概念,以及如何用信号量实现多进程(线程)同步控制的方法。熟悉
pthread_create()
、sem_wait()
、sem_post()
、pthread_mutex_init
、pthread_mutex_lock
、pthread_mutex_unlock
、pthread_join
函数的使用,复习C语言的相关内容。
下面是对
pthread_create()
、sem_wait()
、sem_post()
、pthread_mutex_init
、pthread_mutex_lock
、pthread_mutex_unlock
、pthread_join
函数的简要说明:
pthread_create(): pthread_create()
函数用于创建一个新的线程。它的原型如下:
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg);
```
参数 `thread` 是一个指向线程标识符的指针,`attr` 是线程属性,`start_routine` 是线程开始执行的函数,`arg` 是传递给线程函数的参数。该函数成功时返回 0,失败时返回错误码。
sem_wait(): sem_wait()
函数用于对信号量进行 P 操作(等待操作)。它的原型如下:
int sem_wait(sem_t *sem);
```
参数 `sem` 是指向信号量的指针。如果信号量的值大于 0,则将其减 1;如果信号量的值为 0,则阻塞调用线程直到信号量的值大于 0。该函数成功时返回 0,失败时返回错误码。
sem_post(): sem_post()
函数用于对信号量进行 V 操作(释放操作)。它的原型如下:
int sem_post(sem_t *sem);
```
参数 `sem` 是指向信号量的指针。将信号量的值加 1。该函数成功时返回 0,失败时返回错误码。
pthread_mutex_init: pthread_mutex_init
函数用于初始化互斥锁。它的原型如下:
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
```
参数 `mutex` 是指向互斥锁的指针,`attr` 是互斥锁的属性。该函数成功时返回 0,失败时返回错误码。
pthread_mutex_lock: pthread_mutex_lock
函数用于对互斥锁进行加锁操作。它的原型如下:
int pthread_mutex_lock(pthread_mutex_t *mutex);
```
参数 `mutex` 是指向互斥锁的指针。如果互斥锁当前没有被锁定,则锁定互斥锁。如果互斥锁已经被锁定,调用线程将被阻塞,直到互斥锁被解锁。该函数成功时返回 0,失败时返回错误码。
pthread_mutex_unlock: pthread_mutex_unlock
函数用于对互斥锁进行解锁操作。它的原型如下:
int pthread_mutex_unlock(pthread_mutex_t *mutex);
```
参数 `mutex` 是指向互斥锁的指针。解锁互斥锁,允许其他线程获取该互斥锁。该函数成功时返回 0,失败时返回错误码。
pthread_join: pthread_join
函数用于等待指定的线程结束。它的原型如下:
int pthread_join(pthread_t thread, void **retval);
```
参数 `thread` 是要等待的线程标识符,`retval` 是指向线程返回值的指针。调用线程将被阻塞,直到指定的线程结束。该函数成功时返回 0,失败时返回错误码。
这些函数是在 C 语言中用于多线程编程和同步控制的重要函数。使用它们可以实现线程的创建、互斥锁的操作、信号量的操作以及线程的等待和同步。
信号量(Semaphore)是一种同步原语,用于控制多进程(线程)对共享资源的访问。它可以用来解决多进程(线程)同步和互斥的问题。
临界区(Critical Section)是指一段代码,在同一时间只能被一个进程(线程)执行。在临界区内,对共享资源的访问需要进行同步控制,以避免并发访问导致的错误。
临界资源(Critical Resource)是指被多个进程(线程)共享的资源,如共享内存区、文件等。对于临界资源的访问需要进行同步控制,以确保多个进程(线程)之间的正确协作。
直接制约关系(Direct Constraint)指的是两个进程(线程)之间的依赖关系,其中一个进程(线程)的执行依赖于另一个进程(线程)的某个操作完成。
间接制约关系(Indirect Constraint)指的是两个进程(线程)之间通过共享资源产生的依赖关系,一个进程(线程)的执行依赖于另一个进程(线程)对共享资源的访问。
使用信号量可以实现多进程(线程)的同步控制。信号量可以看作是一个计数器,用于表示可用的资源数量。常用的信号量操作有两个:
sem_wait()
:如果信号量的值大于0,则将其减1;如果信号量的值为0,则阻塞该进程(线程)直到信号量的值大于0。sem_post()
:将信号量的值加1。下面是一个使用信号量实现多进程(线程)同步控制的方法的示例代码:
#include
#include
#include
#define NUM_THREADS 5
sem_t semaphore; // 信号量
void* thread_func(void* thread_id) {
long tid = (long)thread_id;
// 进入临界区之前的操作
printf("Thread %ld is doing something before entering the critical section.\n", tid);
// 进入临界区
sem_wait(&semaphore);
// 临界区内的操作
printf("Thread %ld is in the critical section.\n", tid);
// 离开临界区
sem_post(&semaphore);
// 离开临界区之后的操作
printf("Thread %ld is doing something after leaving the critical section.\n", tid);
pthread_exit(NULL);
}
int main() {
pthread_t threads[NUM_THREADS];
int rc;
long t;
// 初始化信号量
sem_init(&semaphore, 0, 1);
for (t = 0; t < NUM_THREADS; t++) {
printf("Creating thread %ld\n", t);
rc = pthread_create(&threads[t], NULL, thread_func, (void*)t);
if (rc) {
printf("Error: return code from pthread_create() is %d\n", rc);
return -1;
}
}
// 等待所有线程结束
for (t = 0; t < NUM_THREADS; t++) {
pthread_join(threads[t], NULL);
}
// 销毁信号量
sem_destroy(&semaphore);
return 0;
}
在上面的示例代码中,使用了sem_wait()
和sem_post()
函数来实现对信号量的操作,控制多个线程对临界区的访问。pthread_create()
函数用于创建线程,pthread_join()
函数用于等待线程结束。sem_init()
函数用于初始化信号量,sem_destroy()
函数用于销毁信号量。
//pv操作:生产者与消费者经典问题
//author:leaf
#include
#include
#include
#include
#include
#define M 32 /*缓冲数目*/
#define P(x) sem_wait(&x)
#define V(x) sem_post(&x)
int in = 0; /*生产者放置产品的位置*/
int out = 0; /*消费者取产品的位置*/
int buff[M] = {0}; /*缓冲初始化为0, 开始时没有产品*/
sem_t empty_sem; /*同步信号量,当满了时阻止生产者放产品*/
sem_t full_sem; /*同步信号量,当没产品时阻止消费者消费*/
pthread_mutex_t mutex; /*互斥信号量, 一次只有一个线程访问缓冲*/
/*
*output the buffer
*/
void print()
{
int i;
for(i = 0; i < M; i++)
printf("%d ", buff[i]);
printf("\n");
}
/*
*producer
*/
void *producer()
{
for(;;)
{
sleep(1);
P(empty_sem);
pthread_mutex_lock(&mutex);
in = in % M;
printf("(+)produce a product. buffer:");
buff[in] = 1;
print();
++in;
pthread_mutex_unlock(&mutex);
V(full_sem);
}
}
/*
*consumer
*/
void *consumer()
{
for(;;)
{
sleep(2);
P(full_sem);
pthread_mutex_lock(&mutex);
out = out % M;
printf("(-)consume a product. buffer:");
buff[out] = 0;
print();
++out;
pthread_mutex_unlock(&mutex);
V(empty_sem);
}
}
void sem_mutex_init()
{
/*
*semaphore initialize
*/
int init1 = sem_init(&empty_sem, 0, M);
int init2 = sem_init(&full_sem, 0, 0);
if( (init1 != 0) && (init2 != 0))
{
printf("sem init failed \n");
exit(1);
}
/*
*mutex initialize
*/
int init3 = pthread_mutex_init(&mutex, NULL);
if(init3 != 0)
{
printf("mutex init failed \n");
exit(1);
}
}
int main()
{
pthread_t id1;
pthread_t id2;
int i;
int ret;
sem_mutex_init();
/*create the producer thread*/
ret = pthread_create(&id1, NULL, producer, NULL);
if(ret != 0)
{
printf("producer creation failed \n");
exit(1);
}
/*create the consumer thread*/
ret = pthread_create(&id2, NULL, consumer, NULL);
if(ret != 0)
{
printf("consumer creation failed \n");
exit(1);
}
pthread_join(id1,NULL);
pthread_join(id2,NULL);
exit(0);
}
#include
#include
#include
#include
#include
#define M 32 /*缓冲数目*/
#define N_PRODUCERS 2 /*生产者线程数目*/
#define N_CONSUMERS 2 /*消费者线程数目*/
#define P(x) sem_wait(&x)
#define V(x) sem_post(&x)
int in = 0; /*生产者放置产品的位置*/
int out = 0; /*消费者取产品的位置*/
int buff[M] = {0}; /*缓冲初始化为0, 开始时没有产品*/
sem_t empty_sem; /*同步信号量,当满了时阻止生产者放产品*/
sem_t full_sem; /*同步信号量,当没产品时阻止消费者消费*/
pthread_mutex_t mutex; /*互斥信号量, 一次只有一个线程访问缓冲*/
/*
*output the buffer
*/
void print()
{
int i;
for(i = 0; i < M; i++)
printf("%d ", buff[i]);
printf("\n");
}
/*
*producer
*/
void *producer()
{
for(;;)
{
sleep(1);
P(empty_sem);
pthread_mutex_lock(&mutex);
in = in % M;
printf("Producer %lu (+) produce a product. buffer:", pthread_self());
buff[in] = 1;
print();
++in;
pthread_mutex_unlock(&mutex);
V(full_sem);
}
}
/*
*consumer
*/
void *consumer()
{
for(;;)
{
sleep(2);
P(full_sem);
pthread_mutex_lock(&mutex);
out = out % M;
printf("Consumer %lu (-) consume a product. buffer:", pthread_self());
buff[out] = 0;
print();
++out;
pthread_mutex_unlock(&mutex);
V(empty_sem);
}
}
void sem_mutex_init()
{
/*
*semaphore initialize
*/
int init1 = sem_init(&empty_sem, 0, M);
int init2 = sem_init(&full_sem, 0, 0);
if( (init1 != 0) && (init2 != 0))
{
printf("sem init failed \n");
exit(1);
}
/*
*mutex initialize
*/
int init3 = pthread_mutex_init(&mutex, NULL);
if(init3 != 0)
{
printf("mutex init failed \n");
exit(1);
}
}
int main()
{
pthread_t producers[N_PRODUCERS];
pthread_t consumers[N_CONSUMERS];
int i;
int ret;
sem_mutex_init();
/*create the producer threads*/
for (i = 0; i < N_PRODUCERS; i++) {
ret = pthread_create(&producers[i], NULL, producer, NULL);
if(ret != 0)
{
printf("producer creation failed \n");
exit(1);
}
}
/*create the consumer threads*/
for (i = 0; i < N_CONSUMERS; i++) {
ret = pthread_create(&consumers[i], NULL, consumer, NULL);
if(ret != 0)
{
printf("consumer creation failed \n");
exit(1);
}
}
/* join the producer threads */
for (i = 0; i < N_PRODUCERS; i++) {
pthread_join(producers[i], NULL);
}
/* join the consumer threads */
for (i = 0; i < N_CONSUMERS; i++) {
pthread_join(consumers[i], NULL);
}
exit(0);
}
问题:
竞争条件:在并发执行的情况下,如果没有正确处理线程之间的竞争条件,可能导致数据不一致或者死锁等问题。
死锁:如果没有正确使用互斥锁和信号量,可能会导致线程之间的死锁,即所有线程都被阻塞,无法继续执行。
缓冲区溢出或下溢:如果没有正确控制生产者和消费者的速度或没有合理地处理缓冲区满或空的情况,可能会导致缓冲区溢出或下溢,造成数据丢失或错误。
收获:
理解并发编程:通过实验,可以加深对并发编程的理解,包括线程间的竞争条件、同步机制和互斥机制等概念。
学习信号量和互斥锁的使用:通过实验中对信号量和互斥锁的使用,可以学习如何使用这些同步机制来保护共享资源,避免竞争条件和死锁。
多生产者和多消费者问题的处理:通过改进程序,将单生产者和单消费者问题扩展为多生产者和多消费者问题,可以学习如何处理更复杂的并发场景,包括线程的创建和管理、同步机制的调整等。
调试并发程序:在实验过程中,可能会遇到各种并发问题,如死锁、数据竞争等。通过调试并发程序,可以学习如何识别和解决这些问题,并加深对并发调试技巧的理解。
抢占式
进程的CPU使用权被抢
非抢占式
进程主动放弃CPU
先来先服务(First-Come, First-Served,FCFS)调度算法:
FCFS调度算法按照作业或进程到达的先后顺序进行调度,采用非抢占式调度方式。它适用于批处理系统或对响应时间要求不高的环境。然而,它可能导致长作业优先(Longest Job First,LJF)效应,即长作业占用处理机时间较长,导致短作业的等待时间增加。
优点:
缺点:
最短作业优先(Shortest Job First,SJF)调度算法:
最短作业优先(SJF)调度算法根据作业或进程的执行时间进行调度,选择执行时间最短的作业或进程优先执行。它能够最大程度地减少平均等待时间,对短作业有较好的响应性能。然而,它需要预先知道作业或进程的执行时间,对实时系统或无法准确预测执行时间的情况不适用。此外,它可能导致长作业等待时间较长,容易出现饥饿现象。
优点:
缺点:
优先级调度算法:
优先级调度算法根据作业或进程的优先级进行调度,选择优先级最高的作业或进程优先执行。它可以灵活控制作业或进程的执行顺序,适用于根据实时性要求进行动态调整。然而,需要合理设置优先级,否则可能导致低优先级的作业或进程长时间等待。此外,可能出现优先级反转问题,即高优先级作业或进程被低优先级作业或进程长时间阻塞。
优点:
缺点:
时间片轮转(Round Robin,RR)调度算法:
时间片轮转(RR)调度算法公平地分配处理机时间,保证每个作业或进程都能够及时执行。它适用于多任务环境,能够提供较好的响应性能。然而,时间片的长度需要合理设置,过长的时间片会导致响应时间变长,而过短的时间片会增加上下文切换的开销。此外,长作业可能仍然占用较多的时间片,导致短作业的等待时间增加。
优点:
缺点:
死锁是指两个或多个进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,如果没有外力干涉,它们都无法继续执行。
竞争资源:如果系统中有限的资源被多个进程或线程同时请求,而每个进程或线程都持有部分资源而等待其他进程释放其所占有的资源,那么就会出现死锁。例如,两个进程都获取了打开文件的描述符,并且每个进程都在等待另一个进程释放其文件,这就是一种死锁情况。
进程/线程推进顺序不当:如果进程或线程请求资源的顺序不一致,就可能发生死锁。例如,考虑两个进程A和B,A拥有资源1并请求资源2,B拥有资源2并请求资源1,如果它们都按照这个顺序请求资源,那么就会陷入死锁。
信号量使用不当:例如生产者-消费者问题中,如果使用实现互斥的p操作在同步的p操作之前就可能导致死锁。
鸵鸟策略是一种忽略死锁的策略,即忽略死锁的状态,并假设它不会发生。这种策略的问题在于,一旦死锁真正发生,系统可能无法恢复正常操作。
预防策略是在发生死锁之前避免它发生的策略。这种策略通常包括限制系统资源的使用,例如限制同时打开的文件数量,或者要求进程在请求资源之前必须先释放其已经持有的所有资源。
避免策略是在发生死锁之前检测并避免它发生的策略。这种策略通常包括使用定时器来检测长时间等待资源的进程,并让这些进程释放它们已经持有的资源。
public class BankerAlgorithm {
private int[] maxResource; // 最大资源需求
private int[] allocResource; // 已分配资源
private int[] freeResource; // 可用资源数量
private int[] needResource; // 进程所需资源数量
private int numProcesses; // 进程数量
private int numResources; // 资源数量
public BankerAlgorithm(int[] maxResource, int[] allocResource, int[] freeResource, int[] needResource, int numProcesses, int numResources) {
this.maxResource = maxResource;
this.allocResource = allocResource;
this.freeResource = freeResource;
this.needResource = needResource;
this.numProcesses = numProcesses;
this.numResources = numResources;
}
public boolean isSafe() {
int[] available = new int[numResources]; // 可用的资源数量
for (int i = 0; i < numProcesses; i++) {
available[i] = freeResource[i];
}
boolean isSafe = true; // 是否安全
for (int i = 0; i < numProcesses; i++) {
if (needResource[i] <= available[i]) {
available[i] += allocResource[i];
} else {
isSafe = false;
break;
}
}
if (isSafe) {
for (int i = 0; i < numProcesses; i++) {
if (needResource[i] > available[i]) {
isSafe = false;
break;
}
}
}
return isSafe;
}
public static void main(String[] args) {
int[] maxResource = {10, 10, 10}; // 最大资源需求
int[] allocResource = {5, 3, 7}; // 已分配资源
int[] freeResource = {8, 5, 3}; // 可用资源数量
int[] needResource = {1, 1, 1}; // 进程所需资源数量
int numProcesses = 2; // 进程数量
int numResources = 3; // 资源数量
BankerAlgorithm bankerAlgorithm = new BankerAlgorithm(maxResource, allocResource, freeResource, needResource, numProcesses, numResources);
boolean isSafe = bankerAlgorithm.isSafe();
System.out.println("系统是否处于安全状态:" + isSafe);
}
}
检测和解除策略是在发生死锁之后检测并解除它发生的策略。这种策略通常包括定期检查系统状态以检测死锁,一旦检测到死锁,就采取措施(如终止和重启进程)来解除死锁。