多线程和线程同步复习

多线程和线程同步复习

  • 进程线程区别
  • 创建线程
  • 线程退出
  • 线程回收
    • 全局写法
    • 传参写法
  • 线程分离
  • 线程同步
    • 同步方式
  • 互斥锁
    • 互斥锁进行线程同步
  • 死锁
  • 读写锁
    • 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.xdnf.cn/news/8934.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

MySQL_第13章_视图

1. 常见的数据库对象 2. 视图概述 2.1 为什么使用视图&#xff1f; 视图一方面可以使用表的一部分而不是所有的表&#xff0c;另一方面也可以针对不同的用户制定不同的查询视图。 2.2 视图的理解 视图是一种虚拟表&#xff0c;本身是不具有数据的&#xff0c;占用很少的内存…

Python数据分析-Netflix数据分析和可视化

一、研究背景 在当今时代&#xff0c;流媒体技术迅猛发展&#xff0c;如风暴般席卷全球娱乐产业&#xff0c;重塑了大众的娱乐消费模式。Netflix 在这一潮流中一马当先&#xff0c;成为全球首屈一指的在线流媒体平台。自 2007 年开启流媒体服务后&#xff0c;Netflix 就马不停…

数据集市是什么?有什么优势?

一、数据集市是什么&#xff1f; 1、数据集市的产生背景&#xff1a; 因为数据仓库的工作范围和成本比较巨大&#xff0c;技术部门必须对所有的以全企业的眼光对待任何一次决策分析&#xff0c;这样就变成了成本高、耗时高的大项目&#xff0c;而且这种集中式的数据处理方式往往…

Cross Modal Transformer: Towards Fast and Robust 3D Object Detection

代码地址 https://github.com/junjie18/CMT 1. 引言 在本文中&#xff0c;我们提出了Cross-Modal Transformer&#xff08;CMT&#xff09;&#xff0c;这是一种简单而有效的端到端管道&#xff0c;用于鲁棒的3D对象检测&#xff08;见图1&#xff08;c&#xff09;&#xf…

Oracle数据库 查看SQL执行计划的几种方法

前言 在日常的运维工作中&#xff0c;SQL优化是DBA的进阶技能&#xff0c;SQL优化的前提是要看SQL的执行计划是否正确&#xff0c;下面分享几种查看执行计划的方法&#xff0c;每一种方法都各有各的好处&#xff0c;可以根据特定场景选择某种方法。 一.使用AUTOTRACE查看执行…

简单介绍Nginx服务器的反向代理、负载均衡

欢迎来到“雪碧聊技术”CSDN博客&#xff01; 在这里&#xff0c;您将踏入一个专注于Java开发技术的知识殿堂。无论您是Java编程的初学者&#xff0c;还是具有一定经验的开发者&#xff0c;相信我的博客都能为您提供宝贵的学习资源和实用技巧。作为您的技术向导&#xff0c;我将…

域名+服务器+Nginx+宝塔使用SSL证书配置HTTPS

前言 在我的前面文章里&#xff0c;有写过一篇文章 linux服务器宝塔从头部署别人可访问的网站 在这篇文章&#xff0c;有教学怎么使用宝塔和买的服务器的公网IP&#xff0c;以及教怎么打包vue和springboot去部署不用域名的网站让别人访问 那么&#xff0c;这篇文章将在这个…

Chromium 中chrome.webRequest扩展接口定义c++

一、chrome.webRequest 注意 &#xff1a;从 Manifest V3 开始&#xff0c;"webRequestBlocking" 权限不再适用于大多数扩展程序。以 "declarativeNetRequest" 为例&#xff0c;它允许使用 declarativeNetRequest API。除了 "webRequestBlocking&quo…

.NET中通过C#实现Excel与DataTable的数据互转

在.NET框架中&#xff0c;使用C#进行Excel数据与DataTable之间的转换是数据分析、报表生成、数据迁移等操作中的常见需求。这一过程涉及到将Excel文件中的数据读取并加载至DataTable中&#xff0c;以便于利用.NET提供的丰富数据处理功能进行操作&#xff0c;同时也包括将DataTa…

多个NVR同时管理EasyNVR多品牌NVR管理工具/设备:IP常见问题解决方案

随着视频监控技术的不断发展&#xff0c;NVR&#xff08;网络视频录像机&#xff09;已经成为现代安防系统的重要组成部分。而为了更高效地管理多个品牌的NVR设备&#xff0c;EasyNVR这一多品牌NVR管理工具应运而生。然而&#xff0c;在实际使用过程中&#xff0c;尤其是在多个…

虚幻引擎 CEO 谈元宇宙:发展、策略与布局

在当今科技领域&#xff0c;元宇宙无疑是最热门的话题之一。Epic Games 首席执行官 Tim Sweeney 对元宇宙的未来发展充满信心&#xff0c;他认为开放元宇宙将融合娱乐、游戏和科技产业&#xff0c;带来一个光明的未来。本文将深入探讨采访中的关键内容&#xff0c;分析元宇宙的…

支付宝与华为终端联手,移动支付即将进入“碰时代”

大家好&#xff0c;我是小悟。 支付宝与华为终端强强联手&#xff0c;达成了战略合作&#xff01;这可不仅仅是个简单的合作哦&#xff0c;它预示着我们的移动支付方式即将迎来一场革命性的变革&#xff0c;正式进入“碰时代”&#xff01; 支付宝&#xff0c;作为全球领先的…

常用机器人算法原理介绍

一、引言 随着科技的不断发展&#xff0c;机器人技术在各个领域得到了广泛应用。机器人算法是机器人实现各种功能的核心&#xff0c;它决定了机器人的行为和性能。本文将介绍几种常用的机器人算法原理&#xff0c;包括路径规划算法、定位算法和运动控制算法。 二、路径规划算法…

【go从零单排】迭代器(Iterators)

&#x1f308;Don’t worry , just coding! 内耗与overthinking只会削弱你的精力&#xff0c;虚度你的光阴&#xff0c;每天迈出一小步&#xff0c;回头时发现已经走了很远。 &#x1f4d7;概念 在 Go 语言中&#xff0c;迭代器的实现通常不是通过语言内置的迭代器类型&#x…

Java 连接操作 MySQL 数据库(增删查改操作)

环境 MySQL 5.5 版本eclipseMySQL 连接驱动 mysql-connector-java-5.1.18-bin.jar mysql8.0之前的版本与之后的版本使用的jar包是不同的&#xff0c;在使用时也有一定的区别。这里&#xff0c;我的 MySQL 版本为 5.5。 准备工作 将 jar 包添加到项目中&#xff0c;右键项目&a…

STL---迭代器

本文来源&#xff1a;《C语言程序设计》第10章 理解迭代器对于理解STL框架并掌握STL的使用至关重要。 迭代器是泛化的指针&#xff0c;STL算法利用迭代器对存储在容器中的元素序列进行遍历&#xff0c;迭代器提供了访问容器中每个元素的方法。 虽然指针也是一种迭代器&#…

TSMI252012PMX-3R3MT功率电感详细解析

TSMI252012PMX-3R3MT功率电感详细解析 一、引言 在现代电子设备的不断小型化和高性能化的趋势下&#xff0c;功率电感作为电路中的关键元件&#xff0c;其性能的好坏直接影响到整个电路的稳定性和效率。TSMI252012PMX-3R3MT作为深圳市时源芯微科技有限公司&#xff08;TimeSo…

Ubuntu22.04安装DataEase

看到DataEase的驾驶舱&#xff0c;感觉比PowerBI要好用一点&#xff0c;于是搭建起来玩玩。Dataease推荐的操作系统是Ubuntu22.04/Centos 7。 下载了Ubuntu22.04和DataEase 最新版本的离线安装包 一.安装ubuntu22.04 在安装的时候&#xff0c;没有顺手设置IP地址信息&#xff…

OpenEuler 下 Docker 安装、配置与测试实例

文章目录 前言1. 环境准备2. 下载 Docker3.配置服务文件4.配置加速器加速下载docker镜像5. 验证 Docker 安装 前言 Docker 安装大致分为包管理器安装、脚本安装、离线手动安装、容器编排工具安装、桌面版安装等&#xff0c;每种安装各有特点&#xff0c;但涉及知识面不少&…

wordpress实用功能A5资源网同款 隐藏下载框 支付框 需要登录才能查看隐藏的内容

实用功能 隐藏下载框 支付框 需要登录才能查看隐藏的内容, 个人网站防天朝申查实测有效 。 登录前&#xff0c;未登录&#xff1a; 登录后&#xff0c;已登录&#xff1a; 功能说明 该代码段的主要功能是隐藏支付框并为未 登录用户显示一条提示信息&#xff0c;告知他们需要…