目录
请设计进程既有同步又有互斥的应用场景,并尝试用信号量机制实现。可尝试用有名或无名信号量代码实现上述过程,并给出代码截图、调试过程和运行结果截图。当交换互斥和同步的P,V操作顺序时,程序运行结果是什么?
请设计进程既有同步又有互斥的应用场景,并尝试用信号量机制实现。可尝试用有名或无名信号量代码实现上述过程,并给出代码截图、调试过程和运行结果截图。当交换互斥和同步的P,V操作顺序时,程序运行结果是什么?
使用无名信号量的代码
#include <stdio.h>
#include <stdlib.h>
#include <semaphore.h>
#include <pthread.h>
#include <unistd.h>
#define BUFFER_SIZE 5
int buffer[BUFFER_SIZE];
int in = 0;
int out = 0;
sem_t mutex;
sem_t full;
sem_t empty;
// producer
void *producer(void *arg) {
int item;
while (1) {
item = rand() % 100;
sem_wait(&empty);
sem_wait(&mutex);
buffer[in]=item;
printf("Producer produced item %d at position %d\n", item, in);
in = (in + 1) % BUFFER_SIZE;
sem_post(&mutex);
sem_post(&full);
sleep(1);
}
return NULL;
}
// consumer
void *consumer(void *arg) {
int item;
while (1) {
sem_wait(&full);
sem_wait(&mutex);
item = buffer[out];
printf("Consumer consumed item %d from position %d\n", item, out);
out = (out + 1) % BUFFER_SIZE;
sem_post(&mutex);
sem_post(&empty);
sleep(2);
}
return NULL;
}
int main() {
pthread_t producer_thread, consumer_thread;
sem_init(&mutex, 0, 1);
sem_init(&full, 0, 0);
sem_init(&empty, 0, BUFFER_SIZE);
pthread_create(&producer_thread, NULL, producer, NULL);
pthread_create(&consumer_thread, NULL, consumer, NULL);
pthread_join(producer_thread, NULL);
pthread_join(consumer_thread, NULL);
sem_destroy(&mutex);
sem_destroy(&full);
sem_destroy(&empty);
return 0;
}
-
信号量的作用(
mutex
、full
和empty)
-
定义了三个信号量:
mutex
、full
和empty
。-
mutex
信号量用于实现对共享缓冲区的互斥访问。其初始值为1,表示初始时缓冲区是可被访问的。在生产者和消费者访问缓冲区时,都需要先获取mutex
信号量,以确保同一时刻只有一个线程(生产者或消费者)在操作缓冲区。 -
full
信号量用于表示缓冲区中已有的数据项数量。初始值为0,因为开始时缓冲区是空的。当生产者向缓冲区放入一个数据项后,会对full
信号量执行sem_post
操作(增加信号量的值);消费者在从缓冲区取数据前,会先执行sem_wait
操作(如果full
信号量的值为0则阻塞,直到有数据可供消费)。 -
empty
信号量用于表示缓冲区中空闲位置的数量。初始值为BUFFER_SIZE
(这里定义为5),表示缓冲区初始时有5个空闲位置。生产者在向缓冲区放入数据前,会先执行sem_wait
操作(如果empty
信号量的值为0则阻塞,直到有空闲位置);消费者从缓冲区取出一个数据项后,会对empty
信号量执行sem_post
操作(增加信号量的值)。
-
-
-
生产者线程(producer)
-
生产者线程的主要任务是生成随机数,并将其放入共享缓冲区。
-
在
while(1)
循环中:-
首先生成一个0到99之间的随机数
item
。 -
然后执行
sem_wait(&empty)
,如果缓冲区没有空闲位置(empty
信号量的值为0),则生产者线程阻塞在这里,直到有空闲位置。 -
接着执行
sem_wait(&mutex)
获取对缓冲区的互斥访问权。 -
将生成的
item
放入缓冲区buffer[in]
,并打印出Producer produced item <item> at position <in>
,这里<item>
是生成的随机数,<in>
是缓冲区的索引位置。 -
更新
in
的值为(in + 1)%BUFFER_SIZE
,以实现循环使用缓冲区。 -
释放对缓冲区的互斥访问权
sem_post(&mutex)
,并执行sem_post(&full)
表示缓冲区中有了一个新的数据项。 -
最后执行
sleep(1)
,暂停1秒后再次循环。
-
-
-
消费者线程(consumer)
-
消费者线程的主要任务是从共享缓冲区中取出数据。
-
在
while(1)
循环中:-
首先执行
sem_wait(&full)
,如果缓冲区中没有数据(full
信号量的值为0),则消费者线程阻塞在这里,直到有数据可供消费。 -
接着执行
sem_wait(&mutex)
获取对缓冲区的互斥访问权。 -
从缓冲区
buffer[out]
中取出数据项item
,并打印出Consumer consumed item <item> from position <out>
,这里<item>
是取出的数据,<out>
是缓冲区的索引位置。 -
更新
out
的值为(out + 1)%BUFFER_SIZE
,以实现循环使用缓冲区。 -
释放对缓冲区的互斥访问权
sem_post(&mutex)
,并执行sem_post(&empty)
表示缓冲区中有了一个新的空闲位置。 -
最后执行
sleep(2)
,暂停2秒后再次循环。
-
-
-
缓冲区位置索引(
in
和out
)-
生产者角度(
in
)-
在生产者函数中,
in
用于确定将生产的物品放入缓冲区的位置。当生产者生成一个新的物品(例如随机数)时,它会将这个物品放入buffer[in]
的位置。 -
in
的初始值为0,表示生产者开始时将物品放入缓冲区的第一个位置。每次成功放入一个物品后,in
的值会更新为(in + 1)%BUFFER_SIZE
。这里的%BUFFER_SIZE
操作实现了循环缓冲区的效果。例如,当BUFFER_SIZE
为5时,如果in
的值为4,下一次in
将变为0,这样就可以循环使用缓冲区的空间,避免了缓冲区溢出的问题。
-
-
消费者角度(
out
)-
在消费者函数中,
out
用于确定从缓冲区中取出物品的位置。消费者从buffer[out]
位置取出物品。 -
与
in
类似,out
的初始值为0,表示消费者开始时从缓冲区的第一个位置取出物品。每次成功取出一个物品后,out
的值会更新为(out + 1)%BUFFER_SIZE
,同样实现了循环使用缓冲区的效果。
-
-
-
示例说明
-
假设
BUFFER_SIZE = 3
。 -
生产者开始生产物品:
-
首先生产一个物品,放入
buffer[0]
(此时in = 0
)。 -
接着生产下一个物品,放入
buffer[1]
(此时in = 1
)。 -
再生产一个物品,放入
buffer[2]
(此时in = 2
)。 -
当再次生产物品时,
in=(2 + 1)%3 = 0
,于是这个新物品将被放入buffer[0]
,覆盖之前在这个位置的物品(如果之前的物品还未被消费)。
-
-
消费者开始消费物品:
-
首先从
buffer[0]
取出物品(此时out = 0
)。 -
接着从
buffer[1]
取出物品(此时out = 1
)。 -
再从
buffer[2]
取出物品(此时out = 2
)。 -
当再次消费物品时,
out=(2 + 1)%3 = 0
,于是将从buffer[0]
取出物品(如果这个位置有新的物品被生产出来)。
-
-
调试过程
结果截图
-
Producer produced item 83 at position 0
-
这表明生产者线程成功生成了一个随机数83,并将其放入了缓冲区的位置0。这一操作过程是:生产者首先通过
sem_wait(&empty)
检查到缓冲区有空位(因为初始时empty
信号量的值为BUFFER_SIZE
),然后通过sem_wait(&mutex)
获取对缓冲区的互斥访问权,将83放入buffer[0]
,最后更新in
为1(下一次将放入位置1),并释放互斥访问权和更新full
信号量。
-
-
Consumer consumed item 83 from position 0
-
这表明消费者线程从缓冲区的位置0成功取出了数据项83。这一操作过程是:消费者首先通过
sem_wait(&full)
检查到缓冲区有数据(因为生产者已经放入了数据并更新了full
信号量),然后通过sem_wait(&mutex)
获取对缓冲区的互斥访问权,从buffer[0]
取出83,最后更新out
为1(下一次将从位置1取数据),并释放互斥访问权和更新empty
信号量。
-
使用有名信号量的代码
无名信号量和有名信号量的区别在于它们的创建和使用方式。无名信号量是通过sem_init()
函数初始化的,它是一个普通的整数变量,可以通过指针来访问和操作。而有名信号量是通过sem_open()
函数创建的,它通过一个名字来访问和操作,可以在不同的进程之间共享。
无名信号量通常用于同一进程内的线程之间的同步和互斥,因为它们是在内存中创建的,并且只能被同一进程内的线程访问。而有名信号量则适用于不同进程之间的同步和互斥,因为它们是通过文件系统创建的,可以被多个进程共享和访问。
在上述代码中,使用了无名信号量来实现生产者消费者问题。如果要使用有名信号量实现相同的功能,需要将代码稍作修改:
-
使用
sem_open()
函数替换sem_init()
函数创建有名信号量。 -
使用
sem_close()
函数关闭有名信号量。 -
使用
sem_unlink()
函数删除有名信号量。
#include <stdio.h>
#include <stdlib.h>
#include <semaphore.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <pthread.h>
#define BUFFER_SIZE 5
int buffer[BUFFER_SIZE];
int in = 0;
int out = 0;
// 有名信号量
sem_t *mutex;
sem_t *full;
sem_t *empty;
// 生产者函数
void *producer(void *arg) {
int item;
while (1) {
item = rand() % 100;
sem_wait(empty);
sem_wait(mutex);
buffer[in] = item;
printf("Producer produced item %d at position %d\n", item, in);
in = (in + 1) % BUFFER_SIZE;
sem_post(mutex);
sem_post(full);
sleep(1);
}
return NULL;
}
// 消费者函数
void *consumer(void *arg) {
int item;
while (1) {
sem_wait(full);
sem_wait(mutex);
item = buffer[out];
printf("Consumer consumed item %d from position %d\n", item, out);
out = (out + 1) % BUFFER_SIZE;
sem_post(mutex);
sem_post(empty);
sleep(2);
}
return NULL;
}
int main() {
pthread_t producer_thread, consumer_thread;
// 创建有名信号量
mutex = sem_open("/mutex_sem", O_CREAT, 0666, 1);
full = sem_open("/full_sem", O_CREAT, 0666, 0);
empty = sem_open("/empty_sem", O_CREAT, 0666, BUFFER_SIZE);
// 创建生产者和消费者线程
pthread_create(&producer_thread, NULL, producer, NULL);
pthread_create(&consumer_thread, NULL, consumer, NULL);
// 等待线程结束
pthread_join(producer_thread, NULL);
pthread_join(consumer_thread, NULL);
// 关闭和删除有名信号量
sem_close(mutex);
sem_close(full);
sem_close(empty);
sem_unlink("/mutex_sem");
sem_unlink("/full_sem");
sem_unlink("/empty_sem");
return 0;
}
交换互斥和同步的P,V操作顺序时,程序运行结果
任务顺序的同步机制没有正确执行,新的任务在获取mutex
信号量时阻塞,造成死锁情况。
应用场景-火车票售票过程
多个售票窗口同时售票。为了保证每个窗口都能正确地处理售票请求,我们需要确保同一时间只有一个窗口能够访问共享资源(例如票库存)。此外,当票库存为空时,所有窗口都应该停止售票操作,直到新的票被补充到库存中。
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <vector>
class TicketSystem {
private:
int ticket_count;
std::mutex mutex_ticket;
std::condition_variable cv;
public:
TicketSystem(int initial_count) : ticket_count(initial_count) {}
bool buyTicket() {
std::unique_lock<std::mutex> lock(mutex_ticket);
if (ticket_count > 0) {
ticket_count--;
std::cout << "成功购买一张票,剩余票数: " + std::to_string(ticket_count) << std::endl;
return true;
}
std::cout << "无票可买" << std::endl;
return false;
}
void refundTicket() {
std::unique_lock<std::mutex> lock(mutex_ticket);
ticket_count++;
std::cout << "成功退票一张,剩余票数: " + std::to_string(ticket_count) << std::endl;
cv.notify_one();
}
};
void passenger(TicketSystem& ticket_system) {
ticket_system.buyTicket();
}
int main() {
TicketSystem ticket_system(10);
// 创建多个乘客线程
std::vector<std::thread> passengers;
for (int i = 0; i < 15; i++) {
passengers.push_back(std::thread(passenger, std::ref(ticket_system)));
}
// 模拟退票
ticket_system.refundTicket();
// 再创建一些乘客尝试购票
for (int i = 0; i < 3; i++) {
passengers.push_back(std::thread(passenger, std::ref(ticket_system)));
}
// 等待所有线程完成
for (auto& th : passengers) {
if (th.joinable()) {
th.join();
}
}
return 0;
}