多线程和线程同步复习

多线程和线程同步复习

  • 进程线程区别
  • 创建线程
  • 线程退出
  • 线程回收
    • 全局写法
    • 传参写法
  • 线程分离
  • 线程同步
    • 同步方式
  • 互斥锁
    • 互斥锁进行线程同步
  • 死锁
  • 读写锁
    • api细说
    • 读写锁进行线程同步
  • 条件变量
    • 生产者消费者案例
    • 问题解答
    • 加强版生产者消费者
  • 总结
  • 信号量
    • 信号量实现生产者消费者同步-->一个资源
    • 信号量实现生产者消费者同步-->多个资源

进程线程区别

  1. 进程是资源分配的最小单位,线程是操作系统调度执行的最小单位。
  2. 进程有自己独立的地址空间, 多个线程共用同一个地址空间
  3. 线程更加节省系统资源, 效率不仅可以保持的, 而且能够更高
    在一个地址空间中多个线程独享: 每个线程都有属于自己的栈区, 寄存器(内核中管理的)
    在一个地址空间中多个线程共享: 代码段, 堆区, 全局数据区, 打开的文件(文件描述符表)都是线程共享的
  4. 线程是程序的最小执行单位, 进程是操作系统中最小的资源分配单位
    每个进程对应一个虚拟地址空间,一个进程只能抢一个CPU时间片
    一个地址空间中可以划分出多个线程, 在有效的资源基础上, 能够抢更多的CPU时间片
  5. CPU的调度和切换: 线程的上下文切换比进程要快的多
  6. 线程更加廉价, 启动速度更快, 退出也快, 对系统资源的冲击小。

创建线程

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>// 子线程的处理代码
void* working(void* arg)
{printf("我是子线程, 线程ID: %ld\n", pthread_self());for(int i=0; i<9; ++i){printf("child == i: = %d\n", i);}return NULL;
}int main()
{// 1. 创建一个子线程pthread_t tid;pthread_create(&tid, NULL, working, NULL);printf("子线程创建成功, 线程ID: %ld\n", tid);// 2. 子线程不会执行下边的代码, 主线程执行printf("我是主线程, 线程ID: %ld\n", pthread_self());for(int i=0; i<3; ++i){printf("i = %d\n", i);}// 休息, 休息一会儿...sleep(1);// >>>>>>>>>>>>>>>>>>>> 让主线程休息一会给子线程运行return 0;
}

子线程被创建出来之后需要抢cpu时间片, 抢不到就不能运行,如果主线程退出了, 虚拟地址空间就被释放了, 子线程就一并被销毁了。
这里的解决方案,让子线程执行完毕, 主线程再退出, 可以在主线程中添加挂起函数 sleep();
在这里插入图片描述

线程退出

子线程退出这个地址空间是存在的不影响主线程,这个线程退出函数实际上是服务于主线程的。这个函数作用是让主线程退出后子线程继续执行,而不回收这块虚拟地址空间,当子线程退出后这块虚拟地址空间就会被操作系统回收。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>// 子线程的处理代码
void* working(void* arg)
{printf("我是子线程, 线程ID: %ld\n", pthread_self());for(int i=0; i<9; ++i){printf("child == i: = %d\n", i);}return NULL;
}int main()
{// 1. 创建一个子线程pthread_t tid;pthread_create(&tid, NULL, working, NULL);printf("子线程创建成功, 线程ID: %ld\n", tid);pthread_exit(NULL);return 0;
}

在这里插入图片描述
可以看出来主线程退出后是不影响子线程的执行

线程回收

#include <pthread.h>
// 这是一个阻塞函数, 子线程在运行这个函数就阻塞
// 子线程退出, 函数解除阻塞, 回收对应的子线程资源, 类似于回收进程使用的函数 wait()
int pthread_join(pthread_t thread, void **retval);

全局写法

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>struct Test
{int num;int age;
};
struct Test t;
// 子线程的处理代码
void* working(void* arg)
{for(int i=0; i<5; ++i){printf("子线程:i = %d\n", i);}printf("子线程ID: %ld\n", pthread_self());//    struct Test t;  >>>>>>>>>>> errt.num=100;t.age=66;pthread_exit(&t);return NULL;
}
int main()
{pthread_t tid;pthread_create(&tid, NULL, working, NULL);printf("主线程:%ld\n",pthread_self());void* ptr;pthread_join(tid,&ptr);struct Test* pt=(struct Test*)ptr;printf("num: %d,age = %d\n",pt->num,pt->age);return 0;
}

在这里插入图片描述
pthread_join(tid, &ptr); 的作用是等待子线程 tid 执行完毕,并将子线程通过 pthread_exit 返回的指针值保存到 ptr 中。
在这里插入图片描述

传参写法

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>struct Test
{int num;int age;
};// 子线程的处理代码
void* working(void* arg)
{for(int i=0; i<5; ++i){printf("子线程:i = %d\n", i);}printf("子线程ID: %ld\n", pthread_self());struct Test *t=(struct Test*)arg;t->num=100;t->age=66;pthread_exit(&t);return NULL;
}int main()
{pthread_t tid;struct Test t;pthread_create(&tid, NULL, working, &t);// >>>> 把主线程的栈空间给了子线程printf("主线程:%ld\n",pthread_self());void* ptr;pthread_join(tid,&ptr);printf("num: %d,age = %d\n",t.num,t.age);// >>>>>直接打印即可return 0;
}

pthread_join 只是等待线程 tid 执行完成,并获取线程 tid 的返回值。此时并没有释放或销毁传递给线程的参数 t

线程分离

在某些情况下,程序中的主线程有属于自己的业务处理流程,如果让主线程负责子线程的资源回收,调用pthread_join()只要子线程不退出主线程就会一直被阻塞,主要线程的任务也就不能被执行了。
调用这个函数之后指定的子线程就可以和主线程分离,当子线程退出的时候,其占用的内核资源就被系统的其他进程接管并回收了
当主线程退出的时候如果子线程还在工作直接就没了,因为虚拟地址空间被释放了

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>// 子线程的处理代码
void* working(void* arg)
{printf("我是子线程, 线程ID: %ld\n", pthread_self());for(int i=0; i<9; ++i){printf("child == i: = %d\n", i);}return NULL;
}int main()
{// 1. 创建一个子线程pthread_t tid;pthread_create(&tid, NULL, working, NULL);printf("子线程创建成功, 线程ID: %ld\n", tid);// 2. 子线程不会执行下边的代码, 主线程执行printf("我是主线程, 线程ID: %ld\n", pthread_self());for(int i=0; i<3; ++i){printf("i = %d\n", i);}// 设置子线程和主线程分离pthread_detach(tid);// 让主线程自己退出即可pthread_exit(NULL);return 0;
}

在这里插入图片描述
子线程实际上被操作系统回收了

线程同步

线程同步并不是多个线程同时对内存进行访问,而是按照先后顺序依次进行的。

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <pthread.h>#define MAX 50
// 全局变量
int number;// 线程处理函数
void* funcA_num(void* arg)
{for(int i=0; i<MAX; ++i){int cur = number;cur++;usleep(10);// >>>>>>>>>> 微秒number = cur;printf("Thread A, id = %lu, number = %d\n", pthread_self(), number);}return NULL;
}void* funcB_num(void* arg)
{for(int i=0; i<MAX; ++i){int cur = number;cur++;number = cur;printf("Thread B, id = %lu, number = %d\n", pthread_self(), number);usleep(5);}return NULL;
}int main(int argc, const char* argv[])
{pthread_t p1, p2;// 创建两个子线程pthread_create(&p1, NULL, funcA_num, NULL);pthread_create(&p2, NULL, funcB_num, NULL);// 阻塞,资源回收pthread_join(p1, NULL);pthread_join(p2, NULL);return 0;
}

这里创建了两个线程 funcA_num 和 funcB_num,它们分别对共享全局变量 number 进行加一操作并输出结果。然而,由于缺少对 number 的同步处理,会导致线程竞争,从而出现不一致的结果。这是因为在多线程环境中,number 的读写操作并不是原子性的,所以多个线程可能会对 number 进行相互覆盖的操作,导致不正确的输出结果。
具体解释:如果线程A执行这个过程期间就失去了CPU时间片,线程A被挂起了最新的数据没能更新到物理内存。线程B变成运行态之后从物理内存读数据,很显然它没有拿到最新数据,只能基于旧的数据往后数,然后失去CPU时间片挂起。线程A得到CPU时间片变成运行态,第一件事儿就是将上次没更新到内存的数据更新到内存,但是这样会导致线程B已经更新到内存的数据被覆盖,活儿白干了,最终导致有些数据会被重复数很多次。

同步方式

对于多个线程访问共享资源出现数据混乱的问题,需要进行线程同步。常用的线程同步方式有四种:互斥锁、读写锁、条件变量、信号量。所谓的共享资源就是多个线程共同访问的变量,这些变量通常为全局数据区变量或者堆区变量,这些变量对应的共享资源也被称之为临界资源。
通过锁机制能保证临界区代码最多只能同时有一个线程访问,这样并行访问就变为串行访问了。

互斥锁

一般情况下,每一个共享资源对应一个把互斥锁,锁的个数和线程的个数无关。

// 初始化互斥锁
// *******restrict: 是一个关键字, 用来修饰指针, 只有这个关键字修饰的指针可以访问指向的内存地址, 其他指针是不行的********
int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);
// 释放互斥锁资源            
int pthread_mutex_destroy(pthread_mutex_t *mutex);
// 尝试加锁
int pthread_mutex_trylock(pthread_mutex_t *mutex);

互斥锁进行线程同步

加锁和解锁之间放的是临界区,加锁的是临界区的资源

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <pthread.h>#define MAX 50
// 全局变量
int number=0;
pthread_mutex_t mutex;// 线程处理函数
void* funcA_num(void* arg)
{for(int i=0; i<MAX; ++i){pthread_mutex_lock(&mutex);int cur = number;cur++;usleep(10);number = cur;pthread_mutex_unlock(&mutex);printf("Thread A, id = %lu, number = %d\n", pthread_self(), number);}return NULL;
}void* funcB_num(void* arg)
{for(int i=0; i<MAX; ++i){pthread_mutex_lock(&mutex);int cur = number;cur++;number = cur;pthread_mutex_unlock(&mutex);printf("Thread B, id = %lu, number = %d\n", pthread_self(), number);usleep(5);}return NULL;
}int main(int argc, const char* argv[])
{pthread_t p1, p2;pthread_mutex_init(&mutex,NULL);// 创建两个子线程pthread_create(&p1, NULL, funcA_num, NULL);pthread_create(&p2, NULL, funcB_num, NULL);// 阻塞,资源回收pthread_join(p1, NULL);pthread_join(p2, NULL);pthread_mutex_destroy(&mutex);return 0;
}

死锁

加锁之后忘记解锁
重复加锁, 造成死锁
在程序中有多个共享资源, 因此有很多把锁,随意加锁,导致相互被阻塞

读写锁

之所以称其为读写锁,是因为这把锁既可以锁定读操作,也可以锁定写操作
使用读写锁锁定了读操作,需要先解锁才能去锁定写操作,反之亦然
特性:
使用读写锁的读锁锁定了临界区,线程对临界区的访问是并行的,读锁是共享的。
使用读写锁的写锁锁定了临界区,线程对临界区的访问是串行的,写锁是独占的。
使用读写锁分别对两个临界区加了读锁和写锁,两个线程要同时访问者两个临界区,访问写锁临界区的线程继续运行,访问读锁临界区的线程阻塞,因为写锁比读锁的优先级高。 >>>>>>>>>>>>>>>> 场景:读进程大于写进程数的时候

api细说

// 在程序中对读写锁加读锁, 锁定的是读操作
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);//调用这个函数,如果读写锁是打开的,那么加锁成功;**如果读写锁已经锁定了读操作**,**调用这个函数依然可以加锁成功,因为读锁是共享的**;如果读写锁已经锁定了写操作,调用这个函数的线程会被阻塞。// 在程序中对读写锁加写锁, 锁定的是写操作
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
//调用这个函数,如果读写锁是打开的,那么加锁成功;**如果读写锁已经锁定了读操作或者锁定了写操作,调用这个函数的线程会被阻塞。**

读写锁进行线程同步

8个线程操作同一个全局变量,3个线程不定时写同一全局资源,5个线程不定时读同一全局资源。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>// 全局变量
int number = 0;// 定义读写锁
pthread_rwlock_t rwlock;// 写的线程的处理函数
void* writeNum(void* arg)
{while(1){pthread_rwlock_wrlock(&rwlock);int cur = number;cur ++;number = cur;printf("++写操作完毕, number : %d, tid = %ld\n", number, pthread_self());pthread_rwlock_unlock(&rwlock);// 添加sleep目的是要看到多个线程交替工作sleep(rand() % 10);}return NULL;
}// 读线程的处理函数
// 多个线程可以如果处理动作相同, 可以使用相同的处理函数
// 每个线程中的栈资源是独享
void* readNum(void* arg)
{while(1){pthread_rwlock_rdlock(&rwlock);printf("--全局变量number = %d, tid = %ld\n", number, pthread_self());pthread_rwlock_unlock(&rwlock);sleep(rand() % 10);}return NULL;
}int main()
{// 初始化读写锁pthread_rwlock_init(&rwlock, NULL);// 3个写线程, 5个读的线程pthread_t wtid[3];pthread_t rtid[5];for(int i=0; i<3; ++i){pthread_create(&wtid[i], NULL, writeNum, NULL);}for(int i=0; i<5; ++i){pthread_create(&rtid[i], NULL, readNum, NULL);}// 释放资源for(int i=0; i<3; ++i){pthread_join(wtid[i], NULL);}for(int i=0; i<5; ++i){pthread_join(rtid[i], NULL);}// 销毁读写锁pthread_rwlock_destroy(&rwlock);return 0;
}

在这里插入图片描述

条件变量

严格意义上来说,条件变量的主要作用不是处理线程同步, 而是进行线程的阻塞。如果在多线程程序中只使用条件变量无法实现线程的同步, 必须要配合互斥锁来使用。虽然条件变量和互斥锁都能阻塞线程,但是二者的效果是不一样的,二者的区别如下:
条件变量只有在满足指定条件下才会阻塞线程,如果条件不满足,多个线程可以同时进入临界区,同时读写临界资源,这种情况下还是会出现共享资源中数据的混乱。

生产者消费者案例

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>// 链表的节点
struct Node
{int number;struct Node* next;
};// 定义条件变量, 控制消费者线程
pthread_cond_t cond;
// 互斥锁变量
pthread_mutex_t mutex;
// 指向头结点的指针
struct Node * head = NULL;// 生产者的回调函数
void* producer(void* arg)
{// 一直生产while(1){pthread_mutex_lock(&mutex);// 创建一个链表的新节点struct Node* pnew = (struct Node*)malloc(sizeof(struct Node));// 节点初始化pnew->number = rand() % 1000;// 节点的连接, 添加到链表的头部, 新节点就新的头结点pnew->next = head;// head指针前移head = pnew;printf("+++producer, number = %d, tid = %ld\n", pnew->number, pthread_self());pthread_mutex_unlock(&mutex);// 生产了任务, 通知消费者消费pthread_cond_broadcast(&cond);// 生产慢一点sleep(rand() % 3);}return NULL;
}
// 消费者的回调函数
void* consumer(void* arg)
{while(1){pthread_mutex_lock(&mutex);// 一直消费, 删除链表中的一个节点while(head == NULL)   // 这样写有bug//   while(head == NULL){// 任务队列, 也就是链表中已经没有节点可以消费了// 消费者线程需要阻塞// 线程加互斥锁成功, 但是线程阻塞在这行代码上, 锁还没解开// 其他线程在访问这把锁的时候也会阻塞, 生产者也会阻塞 ==> 死锁// 这函数会自动将线程拥有的锁解开 >>>>>>>>>>>>>>>>> pthread_cond_wait(&cond, &mutex);// 当消费者线程解除阻塞之后, 会自动将这把锁锁上// 这时候当前这个线程又重新拥有了这把互斥锁}// 取出链表的头结点, 将其删除struct Node* pnode = head;printf("--consumer: number: %d, tid = %ld\n", pnode->number, pthread_self());head  = pnode->next;free(pnode);pthread_mutex_unlock(&mutex);sleep(rand() % 3);}return NULL;
}int main()
{// 初始化条件变量pthread_cond_init(&cond, NULL);pthread_mutex_init(&mutex, NULL);// 创建5个生产者, 5个消费者pthread_t ptid[5];pthread_t ctid[5];for(int i=0; i<5; ++i){pthread_create(&ptid[i], NULL, producer, NULL);}for(int i=0; i<5; ++i){pthread_create(&ctid[i], NULL, consumer, NULL);}// 释放资源for(int i=0; i<5; ++i){// 阻塞等待子线程退出pthread_join(ptid[i], NULL);}for(int i=0; i<5; ++i){pthread_join(ctid[i], NULL);}// 销毁条件变量pthread_cond_destroy(&cond);pthread_mutex_destroy(&mutex);return 0;
}

问题解答

pthread_cond_wait(&cond, &mutex) 会在执行时释放锁,这是关键所在。
其它被这个消费者互斥锁阻塞的线程再这之后就会开始抢锁
消费者抢锁的操作是再pthread_cond_wait函数内部实现的
案例分析:前一个消费者消费完了链表为空,假设后面还有一个消费者被这个消费者阻塞后,此时生产者生产数据并调用 pthread_cond_broadcast(&cond) 唤醒所有等待的线程,由于前面一个消费者又一次消费完了,那个阻塞的消费者没有进行循环判断就接着消费就会发生段错误。

加强版生产者消费者

生产者设置上限,消费者设置下限,并且用两个条件变量

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>// 链表的节点
struct Node {int number;struct Node* next;
};// 定义两个条件变量, 分别控制生产者和消费者
pthread_cond_t cond_producer; // 生产者的条件变量
pthread_cond_t cond_consumer; // 消费者的条件变量
// 互斥锁变量
pthread_mutex_t mutex;
// 指向头结点的指针
struct Node * head = NULL;
// 生产者生产的上限
#define MAX_PRODUCE 5
// 生产者生产的计数器
int produced_count = 0;// 生产者的回调函数
void* producer(void* arg)
{while (1) {pthread_mutex_lock(&mutex);// 如果已经生产了 5 个任务,则停止生产if (produced_count >= MAX_PRODUCE) {pthread_mutex_unlock(&mutex);break; // 达到生产上限,退出生产}// 创建一个链表的新节点struct Node* pnew = (struct Node*)malloc(sizeof(struct Node));// 节点初始化pnew->number = rand() % 1000;// 节点的连接, 添加到链表的头部, 新节点就新的头结点pnew->next = head;// head指针前移head = pnew;produced_count++;  // 更新生产计数器printf("+++producer, number = %d, tid = %ld\n", pnew->number, pthread_self());// 生产了任务, 通知消费者消费pthread_cond_signal(&cond_consumer);pthread_mutex_unlock(&mutex);// 生产慢一点sleep(rand() % 3);}return NULL;
}// 消费者的回调函数
void* consumer(void* arg)
{while (1) {pthread_mutex_lock(&mutex);// 一直消费, 删除链表中的一个节点while (head == NULL) {// 如果链表为空, 消费者线程等待生产者的通知pthread_cond_wait(&cond_consumer, &mutex);}// 取出链表的头结点, 将其删除struct Node* pnode = head;printf("--consumer: number: %d, tid = %ld\n", pnode->number, pthread_self());head = pnode->next;free(pnode);pthread_mutex_unlock(&mutex);sleep(rand() % 3);}return NULL;
}int main()
{// 初始化条件变量pthread_cond_init(&cond_producer, NULL);pthread_cond_init(&cond_consumer, NULL);pthread_mutex_init(&mutex, NULL);// 创建5个生产者, 5个消费者pthread_t ptid[5];pthread_t ctid[5];for (int i = 0; i < 5; ++i) {pthread_create(&ptid[i], NULL, producer, NULL);}for (int i = 0; i < 5; ++i) {pthread_create(&ctid[i], NULL, consumer, NULL);}// 阻塞等待线程退出for (int i = 0; i < 5; ++i) {pthread_join(ptid[i], NULL);}for (int i = 0; i < 5; ++i) {pthread_join(ctid[i], NULL);}// 销毁条件变量pthread_cond_destroy(&cond_producer);pthread_cond_destroy(&cond_consumer);pthread_mutex_destroy(&mutex);return 0;
}

总结

不使用条件变量的生产者-消费者模型和使用条件变量的生产者-消费者模型
资源占用:使用条件变量的模型更高效,避免了不必要的 CPU 占用。
延迟:条件变量能更快地响应条件变化,不会因轮询间隔导致延迟。
代码复杂度:使用条件变量需要额外的代码来管理条件的等待和唤醒,但能提高性能。

信号量

信号量用在多线程多任务同步的,一个线程完成了某一个动作就通过信号量告诉别的线程,别的线程再进行某些动作。

信号量实现生产者消费者同步–>一个资源

在这里插入图片描述

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
#include <semaphore.h>
// 链表的节点
struct Node
{int number;struct Node* next;
};
//信号量
sem_t semp;
sem_t semc;struct Node * head = NULL;void* producer(void* arg)
{while(1){sem_wait(&semp);struct Node* pnew = (struct Node*)malloc(sizeof(struct Node));pnew->number = rand() % 1000;pnew->next = head;head = pnew;printf("+++producer, number = %d, tid = %ld\n", pnew->number, pthread_self());sem_post(&semc);sleep(rand() % 3);}return NULL;
}void* consumer(void* arg)
{while(1){sem_wait(&semc);struct Node* pnode = head;printf("--consumer: number: %d, tid = %ld\n", pnode->number, pthread_self());head  = pnode->next;free(pnode);sem_post(&semp);sleep(rand() %3);}return NULL;
}
int main()
{//生产者sem_init(&semp,0,1);//资源只有一个,虽然线程很多但是都是被阻塞的//消费者 --> 资源初始化为0 sem_init(&semc,0,0);pthread_t ptid[5];pthread_t ctid[5];for(int i=0; i<5; ++i){pthread_create(&ptid[i], NULL, producer, NULL);}for(int i=0; i<5; ++i){pthread_create(&ctid[i], NULL, consumer, NULL);}// 释放资源for(int i=0; i<5; ++i){// 阻塞等待子线程退出pthread_join(ptid[i], NULL);}for(int i=0; i<5; ++i){pthread_join(ctid[i], NULL);}sem_destroy(&semp);sem_destroy(&semc);return 0;
}

在这里插入图片描述

信号量实现生产者消费者同步–>多个资源

假设资源数是>1的,此时如果多个生产者同时对链表进行添加数据这样是有问题的!
因此要通过锁来让这些线程线性执行
在这里插入图片描述
上面这种情况消费者是不能够通知消费者去生产的,此时生产者由于消费者都没通知所有生产者也阻塞了,这样的话就没有任何一个线程在工作–>问题大了!

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
#include <semaphore.h>
// 链表的节点
struct Node
{int number;struct Node* next;
};
//信号量
sem_t semp;
sem_t semc;
pthread_mutex_t mutex;struct Node * head = NULL;void* producer(void* arg)
{while(1){sem_wait(&semp);pthread_mutex_lock(&mutex);struct Node* pnew = (struct Node*)malloc(sizeof(struct Node));pnew->number = rand() % 1000;pnew->next = head;head = pnew;printf("+++producer, number = %d, tid = %ld\n", pnew->number, pthread_self());pthread_mutex_lock(&mutex);sem_post(&semc);sleep(rand() % 3);}return NULL;
}void* consumer(void* arg)
{while(1){sem_wait(&semc);pthread_mutex_lock(&mutex);struct Node* pnode = head;printf("--consumer: number: %d, tid = %ld\n", pnode->number, pthread_self());head  = pnode->next;free(pnode);pthread_mutex_lock(&mutex);sem_post(&semp);sleep(rand() %3);}return NULL;
}int main()
{//生产者sem_init(&semp,0,5);//资源只有一个,虽然线程很多但是都是被阻塞的//消费者 --> 资源初始化为0 sem_init(&semc,0,0);pthread_mutex_init(&mutex, NULL);pthread_t ptid[5];pthread_t ctid[5];for(int i=0; i<5; ++i){pthread_create(&ptid[i], NULL, producer, NULL);}for(int i=0; i<5; ++i){pthread_create(&ctid[i], NULL, consumer, NULL);}// 释放资源for(int i=0; i<5; ++i){// 阻塞等待子线程退出pthread_join(ptid[i], NULL);}for(int i=0; i<5; ++i){pthread_join(ctid[i], NULL);}sem_destroy(&semp);sem_destroy(&semc);pthread_mutex_destroy(&mutex);return 0;
}

在这里插入图片描述
在生产者-消费者模型中会出现“解锁和通知消费者之间有生产者加锁”的情况

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/60364.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

FlinkPipelineComposer 详解

FlinkPipelineComposer 详解 原文 背景 在flink-cdc 3.0中引入了pipeline机制&#xff0c;提供了除Datastream api/flink sql以外的一种方式定义flink 任务 通过提供一个yaml文件&#xff0c;描述source sink transform等主要信息 由FlinkPipelineComposer解析&#xff0c…

Zustand浅学习

道阻且长&#xff0c;行而不辍&#xff0c;未来可期 之前只是会使用zustand,也没仔细看过zustand的文档&#xff0c;前段时间一个合约朋友问我前端的zustand怎么用&#xff0c;啊&#xff0c;这&#xff0c;是那个笑起来明媚的不像话的帅哥问我问题诶&#xff0c;那我得认真一下…

海量数据迁移:Elasticsearch到OpenSearch的无缝迁移策略与实践

文章目录 一&#xff0e;迁移背景二&#xff0e;迁移分析三&#xff0e;方案制定3.1 使用工具迁移3.2 脚本迁移 四&#xff0e;方案建议 一&#xff0e;迁移背景 目前有两个es集群&#xff0c;版本为5.2.2和7.16.0&#xff0c;总数据量为700T。迁移过程需要不停服务迁移&#…

【贪心算法】贪心算法三

贪心算法三 1.买卖股票的最佳时机2.买卖股票的最佳时机 II3.K 次取反后最大化的数组和4.按身高排序5.优势洗牌&#xff08;田忌赛马&#xff09; 点赞&#x1f44d;&#x1f44d;收藏&#x1f31f;&#x1f31f;关注&#x1f496;&#x1f496; 你的支持是对我最大的鼓励&#…

Devops业务价值流:敏捷测试最佳实践

在迭代增量开发模式下&#xff0c;我们强调按照用户故事的优先级进行软件小功能的频繁交付。由于迭代周期紧凑&#xff0c;测试与开发活动往往并行进行&#xff0c;测试时间相对有限。为确保在这种快节奏的开发环境中依然能够保持产品质量&#xff0c;我们特制定以下测试阶段的…

el-table 纵向垂直表头处理

项目中表格展示会遇到需要纵向垂直表头情况&#xff0c;下面&#xff0c;我们基于el-table组件来实现这种表格。 以下是这次需要用到的数据表格&#xff0c;已知左侧违章名称是固定的&#xff0c;而月份是不固定的&#xff0c;在后端返回数据格式已确定的情况下&#xff0c;需…

HDFS和HBase跨集群数据迁移 源码

HDFS集群间数据迁移&#xff08;hadoop distcp&#xff09; hadoop distcp \ -pb \ hdfs://XX.14.36.205:8020/user/hive/warehouse/dp_fk_tmp.db/ph_cash_order \ hdfs://XX.18.32.21:8020/user/hive/warehouse/dp_fksx_mart.db/HBase集群间数据&#xff08;hbase ExportSnap…

浅谈单片机的gcc优化级别__以双音频信号发生器为例

IDE&#xff1a; CLion HOST&#xff1a; Windows 11 MinGW&#xff1a;x86_64-14.2.0-release-posix-seh-ucrt-rt_v12-rev0 GCC&#xff1a; arm-gnu-toolchain-13.3.rel1-mingw-w64-i686-arm-none-eabi 一、简介 gcc有多种优化级别&#xff0c;一般不选择的情况下&#x…

Ceph MDS高可用架构探索:从零到一构建多主一备MDS服务

文章目录 Ceph实现MDS服务多主一备高可用架构当前 mds 服务器状态添加 MDS 服务器验证ceph集群当前状态当前的文件系统状态设置处于激活状态 mds 的数量MDS 高可用优化分发配置文件并重启 mds 服务 Ceph实现MDS服务多主一备高可用架构 Ceph 的元数据服务&#xff08;MDS&#…

PySpark 数据处理实战:从基础操作到案例分析

Spark 的介绍与搭建&#xff1a;从理论到实践_spark环境搭建-CSDN博客 Spark 的Standalone集群环境安装与测试-CSDN博客 PySpark 本地开发环境搭建与实践-CSDN博客 Spark 程序开发与提交&#xff1a;本地与集群模式全解析-CSDN博客 Spark on YARN&#xff1a;Spark集群模式…

使用GPT-SoVITS训练语音模型

1.项目演示 阅读单句话 1725352713141 读古诗 1725353700203 2.项目环境 开发环境&#xff1a;linux 机器配置如下&#xff1a;实际使用率百分之二十几&#xff0c; 3.开发步骤 1.首先是准备数据集&#xff0c;要求是wav格式&#xff0c;一到两个小时即可&#xff0c; 2.…

Python学习从0到1 day27 Python 高阶技巧 ③ 设计模式 — 单例模式

此去经年&#xff0c;再难同游 —— 24.11.11 一、什么是设计模式 设计模式是一种编程套路&#xff0c;可以极大的方便程序的开发最常见、最经典的设计模式&#xff0c;就是我们所学习的面向对象了。 除了面向对象外,在编程中也有很多既定的套路可以方便开发,我们称之为设计模…

3.2 软件需求:面对过程分析模型

面对过程分析模型 1. 需求分析的模型概述1.1 面对过程分析模型-结构化分析方法1.2 结构化分析的过程 2. 功能模型&#xff1a;数据流图初步2.1 加工2.2 外部实体&#xff08;数据源点/终点&#xff09;2.3 数据流2.4 数据存储2.5 注意事项 3. 功能模型&#xff1a;数据流图进阶…

Android Studio 运行模拟器无法打开avd

问题&#xff1a;已经下载了HAXM 打开模拟器时还是提示未下载HAXM&#xff0c;无法打开avd 解决方案&#xff1a; 控制面板 -> 启动或关闭Windows功能&#xff0c;打开图下两项&#xff0c;后重启电脑重启Android Studio&#xff1a;

Qt文件系统-二进制文件读写

实例功能概述 除了文本文件之外&#xff0c;其他需要按照一定的格式定义读写的文件都称为二进制文件。每种格式的二进制文件都有自己的格式定义&#xff0c;写入数据时按照一定的顺写入&#xff0c;读出时也按照相应的顺读出。例如地球物理中常用的SEG-Y格式文件&#xff0c;必…

ARXML汽车可扩展标记性语言规范讲解

ARXML: Automotive Extensible Markup Language &#xff08;汽车可扩展标记语言&#xff09; xmlns: Xml name space &#xff08;xml 命名空间&#xff09; xsd: Xml Schema Definition (xml 架构定义) 1、XML与HTML的区别&#xff0c;可扩展。 可扩展&#xff0c;主要是…

游戏引擎学习第六天

这节讲的内容比较多: 参考视频:https://www.bilibili.com/video/BV1apmpYVEQu/ XInput 是微软提供的一个 API&#xff0c;用于处理 Windows 平台上 Xbox 控制器&#xff08;包括有线和无线&#xff09;及其他游戏控制器的输入。它为开发者提供了一组函数&#xff0c;用于查询控…

vivado+modelsim: xxx is not a function name

xxx is not a function name vivado问题:xxx is not a function name原因 vivado问题:xxx is not a function name 在写verilog modelsim仿真时&#xff0c;遇到error&#xff1a;xxx is not a function name。 原因 该变量xxx在仿真文件里&#xff0c;如下图红框所示&#…

云计算在教育领域的应用

&#x1f493; 博客主页&#xff1a;瑕疵的CSDN主页 &#x1f4dd; Gitee主页&#xff1a;瑕疵的gitee主页 ⏩ 文章专栏&#xff1a;《热点资讯》 云计算在教育领域的应用 云计算在教育领域的应用 云计算在教育领域的应用 引言 云计算概述 定义与原理 发展历程 云计算的关键技…

立体工业相机提升工业自动化中的立体深度感知

深度感知对仓库机器人应用至关重要&#xff0c;尤其是在自主导航、物品拾取与放置、库存管理等方面。 通过将深度感知与各种类型的3D数据&#xff08;如体积数据、点云、纹理等&#xff09;相结合&#xff0c;仓库机器人可以在错综复杂环境中实现自主导航&#xff0c;物品检测…