#include
#include
#include
#include
// 创建一个互斥量
pthread_mutex_t mutex;
struct Node{
int num;
struct Node *next;
};
// 头结点
struct Node * head = NULL;
void * producer(void * arg) {
// 不断的创建新的节点,添加到链表中
while(1) {
pthread_mutex_lock(&mutex);
struct Node * newNode = (struct Node *)malloc(sizeof(struct Node));
newNode->next = head;//头插法:将新节点查到head后面
head = newNode;
newNode->num = rand() % 1000;
printf("add node, num : %d, tid : %ld\n", newNode->num, pthread_self());
pthread_mutex_unlock(&mutex);
usleep(100);
}
return NULL;
}
void * customer(void * arg) {
while(1) {
pthread_mutex_lock(&mutex);
// 保存头结点的指针
struct Node * tmp = head;
// 判断是否有数据
if(head != NULL) {
// 有数据
head = head->next;
printf("del node, num : %d, tid : %ld\n", tmp->num, pthread_self());
free(tmp);
pthread_mutex_unlock(&mutex);
usleep(100);
} else {
// 没有数据
pthread_mutex_unlock(&mutex);
}
}
return NULL;
}
int main() {
pthread_mutex_init(&mutex, NULL);
// 创建5个生产者线程,和5个消费者线程
pthread_t ptids[5], ctids[5];
for(int i = 0; i < 5; i++) {
pthread_create(&ptids[i], NULL, producer, NULL);
pthread_create(&ctids[i], NULL, customer, NULL);
}
for(int i = 0; i < 5; i++) {
pthread_detach(ptids[i]);
pthread_detach(ctids[i]);
}
while(1) {
sleep(10);
}
pthread_mutex_destroy(&mutex);
pthread_exit(NULL);
return 0;
}
条件变量能比单纯的互斥锁更加高效
怎么个高效法?
答:
如果只是互斥锁,消费者来了看到生产者生产的框子里没有东西,就白跑一趟;
如果是条件变量,消费者来了看到生产者生产的框子里没有东西,条件变量会阻塞在条件变量那一行,并且释放互斥锁,然后通知生产者去生产,直到生产者生产出来至少一个并通知条件变量时,条件变量才会继续往下进行并且再加上互斥锁,不白跑一趟
/*
条件变量的类型 pthread_cond_t
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
int pthread_cond_destroy(pthread_cond_t *cond);
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
- 等待,调用了该函数,线程会阻塞。
int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime);
- 等待多长时间,调用了这个函数,线程会阻塞,直到指定的时间结束。
int pthread_cond_signal(pthread_cond_t *cond);
- 唤醒一个或者多个等待的线程
int pthread_cond_broadcast(pthread_cond_t *cond);//broadcast广播
- 唤醒所有的等待的线程
*/
#include
#include
#include
#include
// 创建一个互斥量
pthread_mutex_t mutex;
// 创建条件变量
pthread_cond_t cond;
struct Node{
int num;
struct Node *next;
};
// 头结点
struct Node * head = NULL;
void * producer(void * arg) {
// 不断的创建新的节点,添加到链表中
while(1) {
pthread_mutex_lock(&mutex);
struct Node * newNode = (struct Node *)malloc(sizeof(struct Node));
newNode->next = head;
head = newNode;
newNode->num = rand() % 1000;
printf("add node, num : %03d, tid : %ld\n", newNode->num, pthread_self());
// 只要生产了一个,就通知消费者消费
pthread_cond_signal(&cond);//发送信号
pthread_mutex_unlock(&mutex);
usleep(100);
}
return NULL;
}
void * customer(void * arg) {
while(1) {
pthread_mutex_lock(&mutex);
// 保存头结点的指针
struct Node * tmp = head;
// 判断是否有数据
if(head != NULL) {
// 有数据
head = head->next;
printf("del node, num : %03d, tid : %ld\n", tmp->num, pthread_self());//%03d是不满三位,补上前导0
free(tmp);
pthread_mutex_unlock(&mutex);
usleep(100);
} else {
// 没有数据,需要等待
// 当这个函数调用阻塞的时候,会对互斥锁进行解锁,当不阻塞的,继续向下执行,会重新加锁。
pthread_cond_wait(&cond, &mutex);//等待信号=>这个函数很意思:阻塞时会释放互斥锁mutex;不阻塞时会重新加上互斥锁mutex,所以这个函数的后面还要再释放一次互斥锁mutex
pthread_mutex_unlock(&mutex);
}
}
return NULL;
}
int main() {
pthread_mutex_init(&mutex, NULL);
pthread_cond_init(&cond, NULL);
// 创建5个生产者线程,和5个消费者线程
pthread_t ptids[5], ctids[5];
for(int i = 0; i < 5; i++) {
pthread_create(&ptids[i], NULL, producer, NULL);
pthread_create(&ctids[i], NULL, customer, NULL);
}
for(int i = 0; i < 5; i++) {
pthread_detach(ptids[i]);
pthread_detach(ctids[i]);
}
while(1) {
sleep(10);
}
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&cond);
pthread_exit(NULL);
return 0;
}
//信号量
/*
信号量的类型 sem_t
int sem_init(sem_t *sem, int pshared, unsigned int value);
- 初始化信号量
- 参数:
- sem : 信号量变量的地址
- pshared : 0 用在线程间 ,非0 用在进程间
- value : 信号量中的值
int sem_destroy(sem_t *sem);
- 释放资源
int sem_wait(sem_t *sem);
- 对信号量加锁,调用一次对信号量的值-1,如果值为0,就阻塞
int sem_trywait(sem_t *sem);
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
int sem_post(sem_t *sem);
- 对信号量解锁,调用一次对信号量的值+1
int sem_getvalue(sem_t *sem, int *sval);
sem_t psem;
sem_t csem;
init(psem, 0, 8);
init(csem, 0, 0);
producer() {
sem_wait(&psem);
sem_post(&csem)
}
customer() {
sem_wait(&csem);
sem_post(&psem)
}
*/
#include
#include
#include
#include
#include
// 创建一个互斥量
pthread_mutex_t mutex;
// 创建两个信号量
sem_t psem;
sem_t csem;
struct Node{
int num;
struct Node *next;
};
// 头结点
struct Node * head = NULL;
void * producer(void * arg) {
// 不断的创建新的节点,添加到链表中
while(1) {
//sem_wait(&psem)=>p操作
sem_wait(&psem);//加锁,psem信号量-1,如果psem信号量为0了,就阻塞在这;即如果值为0,就会阻塞,如果不为0 ,值才会减
//在408中的操作系统题目里,认为psem>=0就执行,看上去好像有点冲突,其实不是的,
//408中题目的意思是:先"尝试"去-1,减完之后,如果psem>=0就继续执行;如果减完之后psem<0,就不会继续执行,就阻塞在这
pthread_mutex_lock(&mutex);
struct Node * newNode = (struct Node *)malloc(sizeof(struct Node));
newNode->next = head;
head = newNode;
newNode->num = rand() % 1000;
printf("add node, num : %03d, tid : %ld\n", newNode->num, pthread_self());
pthread_mutex_unlock(&mutex);
//sem_post(&csem)=>v操作
sem_post(&csem);//解锁,csem信号量+1
}
return NULL;
}
void * customer(void * arg) {
while(1) {
sem_wait(&csem);//加锁,csem信号量-1
pthread_mutex_lock(&mutex);
// 保存头结点的指针
struct Node * tmp = head;
head = head->next;
printf("del node, num : %03d, tid : %ld\n", tmp->num, pthread_self());
free(tmp);
pthread_mutex_unlock(&mutex);
sem_post(&psem);//解锁,psem信号量+1
}
return NULL;
}
int main() {
pthread_mutex_init(&mutex, NULL);
sem_init(&psem, 0, 8);
sem_init(&csem, 0, 0);
// 创建5个生产者线程,和5个消费者线程
pthread_t ptids[5], ctids[5];
for(int i = 0; i < 5; i++) {
pthread_create(&ptids[i], NULL, producer, NULL);
pthread_create(&ctids[i], NULL, customer, NULL);
}
for(int i = 0; i < 5; i++) {
pthread_detach(ptids[i]);
pthread_detach(ctids[i]);
}
while(1) {
sleep(10);
}
pthread_mutex_destroy(&mutex);
pthread_exit(NULL);
return 0;
}
提问:pv操作中,只有p操作可能会出现阻塞的现象,v操作不会出现阻塞的现象,对吗?
是的,一般来说,在PV操作中,只有P操作可能会出现阻塞的现象,而V操作不会出现阻塞的现象。这是因为P操作需要等待共享资源中有可用的空间或者数据,才能执行成功。在共享资源已满或者空时,执行P操作的线程将被阻塞,直到有其他线程执行相应的V操作后才能继续执行。
相反,V操作并不需要等待任何条件就能执行成功,并且通常只是简单地将计数器加1或从缓冲队列中取出一个数据项。因此,V操作不会被阻塞,也不会导致其他线程阻塞。
需要注意的是,有些实现中可能存在V操作阻塞的情况,如信号量的实现中可能会使用链表等数据结构保存等待V操作的线程。但这仅限于特定的实现方案,不是PV操作的基本规则。
P(S):① 将信号量S的值减1,即S=S-1;② 如果S>=0,则该进程继续执行;否则进程进入等待队列,置为等待状态。
V(S):① 将信号量S的值加1,即S=S+1;② 如果S>0,则该进程继续执行;如果S<=0,则会释放等待队列中第一个等待信号量的进程。(因为步骤①将信号量加1后仍然不大于0,则表示等待队列中有阻塞的进程。)
sem_init() 是一个 POSIX 线程库中的函数,用于初始化一个命名或未命名的信号量。它接受三个参数:
1. s e m : 1.sem: 1.sem:指向要初始化的信号量结构体的指针。
2. p s h a r e d : 2.pshared: 2.pshared:指定信号量是否应该在进程间共享。如果为0,则信号量只能被同一进程中的线程共享;如果为非零值,则信号量可以被多个进程共享。
3. v a l u e : 3.value: 3.value:指定初始化信号量的值。这个值必须是非负数。
sem_init() 的第三个参数,即 value 参数,用于指定信号量的初始值。它的作用是控制在同步操作之前是否允许多个线程或进程同时访问临界资源。如果 value 的值为1,则表示只有一个线程或进程可以访问临界资源;如果 value 的值大于1,则表示多个线程或进程可以同时访问临界资源。
需要注意的是,当 sem_init() 初始化一个信号量时,如果 value 的值小于0,则会产生 undefined behavior(未定义行为)。因此,在使用 sem_init() 函数时,应该保证 value 参数的值是合法的非负整数。
下面是一个示例代码,展示了 value 参数的作用:
#include
#include
#include
#include
#define MAX_COUNT 100000
int count = 0;
sem_t mutex;
void *increment_count(void *arg) {
int i;
for (i = 0; i < MAX_COUNT; i++) {
sem_wait(&mutex);
count += 1;
sem_post(&mutex);
}
pthread_exit(NULL);
}
int main() {
pthread_t t1, t2;
// 初始化一个未命名信号量,只能被同一进程内的线程共享,初始值为1
sem_init(&mutex, 0, 1);
// 创建两个线程分别对count进行累加
pthread_create(&t1, NULL, increment_count, NULL);
pthread_create(&t2, NULL, increment_count, NULL);
// 等待两个线程完成
pthread_join(t1, NULL);
pthread_join(t2, NULL);
// 输出count的值
printf("count = %d\n", count);
// 销毁信号量
sem_destroy(&mutex);
return 0;
}
在这个示例代码中,我们使用了一个未命名信号量 mutex 来控制对共享变量 count 的访问。由于 mutex 的初始值为1,所以同一时刻只有一个线程可以访问 count。如果将 mutex 的初始值改为2或更大,就会出现多个线程同时访问 count 的情况,从而导致计算结果不正确。