并发-线程

1, 线程

线程(thread)也是并发的一种形式,线程是比进程更小的活动单位,一个进程中可以有多个线程,线程是进程内部的一个执行分支。

一个进程刚开始时只有一个线程(称之为主线程),后续的代码中可以创建新的线程,可以指定新线程去执行某个函数,这个函数称之为线程函数。

进程内部的多个线程共享该进程内部的所有数据,所以线程间通信相比进程简便很多,如:直接使用全局变量就行

2,线程相关函数

2.1 创建一个新线程

   pthread_t类型变量用来表示一个线程的id,线程id具有唯一性void *(*start_routine) (void *) start_routine 是函数指针,指向一个返回值为void*,参数为void*的函数(这样的函数,称之为线程函数)pthread_create - create a new threadSYNOPSIS#include <pthread.h>int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);thread:pthread_t类型变量的地址,该变量用来保存新线程的idattr:用来指定线程的属性,一般为NULL,表示采取默认属性,如果不想采用默认属性,后续会有函数来修改线程属性。start_routine:线程函数的地址。新线程创建成功后就去执行该函数arg:线程函数的参数Compile and link with -pthread.

2.2 线程结束

(1) 正常结束

该执行的指令都执行完了(线程函数执行完了)

(2) 进程结束了,进程内的所有线程都结束

(3) 在线程执行的任意时刻,调用 pthread_exit 函数,该线程就会退出/结束

    #include <pthread.h>void pthread_exit(void *retval);retval:线程 退出码/返回值 的地址

(4) 被别人干掉

    #include <pthread.h>int pthread_cancel(pthread_t thread);thread:需要被干掉的那个线程的id线程有一个属性,可以决定是否能被别人干掉,可以用一个函数来设置#include <pthread.h>int pthread_setcancelstate(int state, int *oldstate);state: PTHREAD_CANCEL_ENABL3E  可以被干掉(默认属性)PTHREAD_CANCEL_DISABLE  不能被干掉oldstate:用来保存改变之前的属性,如果不关心改变之前的属性,就为NULL        
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
int x = 0;
void func1()//普通函数
{int i;for(i=0;i<100;i++){printf("主线程i=%d\n",i);usleep(100);}
}void* func2(void *arg)//线程函数
{int i;for(i=0;i<100;i++){printf("-----------新线程i=%d\n",i);usleep(100);}
}void* func3(void *arg)//线程函数
{int i;printf("参数:%s\n",(char *)arg);for(i=0;i<100;i++){printf("-----------新线程i=%d\n",i);usleep(100);}
}void* func4(void *arg)//线程函数
{x += 100;int i;printf("参数:%d\n",*((int *)arg));for(i=0;i<100;i++){printf("-----------新线程i=%d\n",i);usleep(100);}}int main()
{//func1();//func1是普通函数,直接调用,并没有并发,先执行完func函数再往下执行pthread_t tid;//用来保存新线程的id//pthread_create(&tid,NULL,func2,NULL);//创建新线程执行 func2函数,并且把参数NULL传递给func2//char buf[] = "线程参数测试";//pthread_create(&tid,NULL,func3,(void*)buf);//创建新线程执行 func3函数,并且把参数buf传递给func3//传递字符串int data = 100;pthread_create(&tid,NULL,func4,(void*)&data);//创建新线程执行 func4函数,并且把参数&data传递给func4//传递整数int i;for(i=0;i<100;i++){printf("主线程i=%d\n",i);usleep(100);}printf("x=%d\n",x);return 0;//进程结束了,所有线程立马都结束//pthread_exit(NULL);//只是退出当前线程(主线程),其他线程如果没有执行完,不会结束
}

2.3 资源回收

一个线程结束了,并不代表所有资源都被释放了,有两种方式回收线程资源:自动回收和手动回收

由一个属性决定是自动回收还是需要手动回收,该属性默认是需要手动回收资源,如果需要自动回收,调用

pthread_detach函数

#include <pthread.h>
int pthread_detach(pthread_t thread);thread:线程id,把这个线程设置为自动回收资源失败返回-1,成功返回0
例如:pthread_detach(pthread_self());//设置自动回收该线程的资源//等待线程结束,并手动回收资源
#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);thread:线程idretval:二级指针,一般是定义一个一级指针变量,把这个变量的地址作为参数传入,成功后,改变了保存了 退出线程的 退出码。如果不需要保存退出码,该参数为NULL失败返回-1,成功返回0二者选一即可

多线程并发也会有和多进程并发一样的问题:访问共享资源时被打断,而造成不可预知的后果

所以多线程并发时,也需要PV操作,可以用之前学过的信号量,但是有更好的方法:线程互斥锁

#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
void * func1(void *arg)
{//pthread_detach(pthread_self());//设置自动回收该线程的资源int i;for(i=0;i<100;i++){printf("-----------新线程i=%d\n",i);usleep(100);} int *p = (int*)malloc(4);*p = 200;//退出码pthread_exit((void*)p);
}int main()
{pthread_t tid;pthread_create(&tid,NULL,func1,NULL);int i;for(i=0;i<100;i++){printf("主线程i=%d\n",i);usleep(100);}int *p;int r = pthread_join(tid,(void*)&p);//等待tid线程结束并 手动回收tid线程的资源if(-1==r){perror("pthread_join失败");}printf("线程退出码:%d\n",*p);free(p);return 0;//进程结束了,所有线程立马都结束
}

3,线程互斥锁

pthread_mutex_t类的变量就是线程互斥锁

3.1 初始化线程互斥锁

#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);  mutex:线程互斥锁的地址attr:互斥锁的属性,一般为NULL,表示默认属性(如:初始化之后为解锁状态)失败返回-1,成功返回0 

3.2 P操作

   #include <pthread.h>
​int pthread_mutex_lock(pthread_mutex_t *mutex);//如果是死锁状态,一直等int pthread_mutex_trylock(pthread_mutex_t *mutex);//尝试获取锁资源int pthread_mutex_timedlock(pthread_mutex_t *restrict mutex,const struct timespec *restrict abstime);//限时等待

3.3 V操作

    int pthread_mutex_unlock(pthread_mutex_t *mutex);

3.4 销毁互斥锁

   #include <pthread.h>int pthread_mutex_destroy(pthread_mutex_t *mutex);
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <sys/shm.h>
#include <string.h>
#include <semaphore.h>pthread_mutex_t mutex;//线程互斥锁//多线程并发时,也需要PV操作,可以用之前学过的信号量,但是有更好的方法:线程互斥锁
int data = 0;//共享资源//信号量
void *func1(void *arg)
{sem_t *s = (sem_t *)arg;int i;for(i=0;i<1000000;i++){sem_wait(s);data++;sem_post(s);}
}//线程互斥锁
void * func2(void *arg)
{int i;for(i=0;i<1000000;i++){pthread_mutex_lock(&mutex);data++;pthread_mutex_unlock(&mutex);}
}int main()
{pthread_mutex_init(&mutex,NULL);pthread_t tid1,tid2;pthread_create(&tid1,NULL,func2,NULL);pthread_create(&tid2,NULL,func2,NULL);pthread_join(tid1,NULL);pthread_join(tid2,NULL);printf("data=%d\n",data);pthread_mutex_destroy(&mutex);return 0;
}

练习:

模拟一个外卖店,有如下要求:

该店菜品有 辣椒炒肉,剁椒鱼头,水煮肉片,麻婆豆腐,红烧肉,糖醋排骨,空心菜,大白菜

每隔时间t1有顾客下单,下单的间隔时间t1随机生成,用 sleep/usleep模拟,下单菜品也是随机生成

两个外卖小哥接单,假设送单时间为t2,也是随机生成,用 sleep/usleep模拟

先下单的一定会被先接单,且同一个外卖小哥只能送完一单才能接下一单

该外卖店最多只接100单,完成则下班

如果累计有20单未被接单,则暂停下单

下单和接单信息打印输出

顾客 -》一个线程

每个外卖小哥 -》 一个线程

void * func1(void *arg)//顾客线程
{while(1)//一直下单,间隔时间为 t1{sleep(t1);下单 -> 下单信息保存到队列  入队}
}void *func2(void *arg)//外卖小哥线程
{while(1)//一直接单,送单时间为 t2{接单  -> 出队模拟送单 -> sleep(t2)}
}
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
#include "queue.h"
char * name[8] = {"辣椒炒肉","剁椒鱼头","水煮肉片","麻婆豆腐","红烧肉","糖醋排骨","空心菜","大白菜"};
int count = 0;//记录下单的总数量
pthread_mutex_t mutex;
pthread_cond_t cond;
pthread_cond_t cond2;
//模拟顾客下单void * func1(void *arg)
{Queue * l = (Queue*)arg;int t1;//顾客下单的间隔时间int index;//顾客下单菜品的下标while(1){t1 = rand() % 10 + 10;//间隔时间假设为 [1,3]秒钟sleep(t1);pthread_mutex_lock(&mutex);if(count >= 100){pthread_mutex_unlock(&mutex);break;        }else{if(l->num < 20){//下单,入队index = rand()%8;push(l,index);count++;printf("顾客下单了,这是第%d单,菜品是:%s\n",count,name[index]);  pthread_cond_signal(&cond);}  else{//订单>=20,暂停下单pthread_cond_wait(&cond2,&mutex);}}pthread_mutex_unlock(&mutex);}
}//模拟外卖小哥接单
void* func2(void *arg)
{Queue * l = (Queue*)arg;int t2;//小哥送单的时间int index;while(1){//接单pthread_mutex_lock(&mutex);if(is_empty(l) == 0){index = get_front(l);//获取订单pop(l);printf("外卖小哥接单,菜品名:%s\n",name[index]);pthread_cond_signal(&cond2);}else{if(count >= 100){pthread_mutex_unlock(&mutex);break;}pthread_cond_wait(&cond,&mutex);printf("一直在循环判断,但是条件一直不成立,浪费CPU\n");}pthread_mutex_unlock(&mutex);t2 = rand() % 1 + 1;//送单时间假设为 [1,3]秒钟sleep(t2);}}int main()
{Queue * l = init_queue();pthread_mutex_init(&mutex,NULL);pthread_cond_init(&cond,NULL);pthread_cond_init(&cond2,NULL);pthread_t tid1,tid2,tid3,tid4;pthread_create(&tid1,NULL,func1,(void*)l);pthread_create(&tid2,NULL,func1,(void*)l);pthread_create(&tid3,NULL,func2,(void*)l);pthread_create(&tid4,NULL,func2,(void*)l);pthread_join(tid1,NULL);pthread_join(tid2,NULL);pthread_join(tid3,NULL);pthread_join(tid4,NULL);pthread_mutex_destroy(&mutex);pthread_cond_destroy(&cond);pthread_cond_destroy(&cond2);destroy_queue(l);return 0;
}

4,线程条件变量

前面的例子中,外卖小哥使订单减少,顾客使订单增加,是一个典型的 消费者-生产者 模型。

如果数据“已满”(超过20单),生产者应该停止生产,如果数据“空了”,消费者停止消费。

问题是消费者怎么知道数据空了?生产者怎么知道数据已满?

常规的做法就是循环判断,一直不间断的进行判断,直到满足我的条件。

生产者的条件:数据没有满

消费者的条件:数据不为空

这种常规做法有一个缺点:浪费CPU资源,有时候一直循环但是都是不满足条件

-》线程条件变量

条件不满足的时候,进行休眠(是指进入阻塞态,让出CPU);

条件满足时,需要别人来唤醒我;(消费者休眠一般由生产者唤醒,生产者休眠一般由消费者唤醒)

4.1 初始化线程条件变量

pthread_cond_t 类型的变量就是线程条件变量
#include <pthread.h>
int pthread_cond_init(pthread_cond_t *cond,const pthread_condattr_t *cattr);cond:要初始化的线程条件变量的首地址cattr:属性,一般为NULL,表示默认属性失败返回-1,同时errno被设置成功返回0

4.2 进入阻塞状态,等待条件满足

   #include <pthread.h>int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);cond:线程条件变量地址mutex:线程互斥锁,在执行 pthread_cond_wait操作前,必须线对 mutex进行lock/p 操作因为在pthread_cond_wait函数内部会对 mutex进行 unlock/V 操作int pthread_cond_timedwait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex,const struct timespec *restrict abstime);

4.3 唤醒正在阻塞的线程

   #include <pthread.h>int pthread_cond_signal(pthread_cond_t *cond);cond:条件变量的地址,唤醒阻塞在该条件变量上的任意一个线程int pthread_cond_broadcast(pthread_cond_t *cond);//broadcast 广播cond:条件变量的地址,唤醒阻塞在该条件变量上的所有线程

4.4 销毁线程条件变量

int pthread_cond_destroy(pthread_cond_t *cond);cond:条件变量的地址

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

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

相关文章

git提交到github个人记录

windows下git下载 1.进入git官网https://git-scm.com/downloads/win 一直默认选项即可 2.在settings中SSH and GPG keys中Add SSH key 3.选择git cmd git使用 1.配置用户名&#xff0c;和邮箱 git config --global user.email "youexample.com" git config --g…

aws(学习笔记第六课) AWS的虚拟私有,共有子网以及ACL,定义公网碉堡主机子网以及varnish反向代理

aws(学习笔记第六课) AWS的虚拟私有&#xff0c;共有子网以及ACL&#xff0c;定义公网碉堡主机子网以及varnish反向代理 学习内容&#xff1a; AWS的虚拟私有&#xff0c;共有子网以及ACL定义公网碉堡主机子网&#xff0c;私有子网和共有子网以及varnish反向代理 1. AWS的虚拟…

深入理解WPF中的命令机制

Windows Presentation Foundation&#xff08;WPF&#xff09;是微软推出的一种用于构建桌面客户端应用程序的技术。它被认为是现代Windows应用程序的基础&#xff0c;具有强大的图形和媒体处理能力。在WPF中&#xff0c;“命令”是一个重要的概念&#xff0c;它为应用程序开发…

如何在算家云搭建Video-Infinity(视频生成)

一、模型介绍 Video-Infinity是一个先进的视频生成模型&#xff0c;使用多个 GPU 快速生成长视频&#xff0c;无需额外训练。它能够基于用户提供的文本或图片提示&#xff0c;创造出高质量、多样化的视频内容。 二、模型搭建流程 1.大模型 Video-Infinity 一键使用 基础环境…

Nest.js 实战 (十四):如何获取客户端真实 IP

问题解析 在 Nest.js 应用中&#xff0c;当你试图通过 request.ip 获取客户端的 IP 地址时&#xff0c;如果总是返回 ::1 或者 ::ffff:127.0.0.1&#xff0c;这通常意味着请求来自本地主机。 因为在前后端分离应用中&#xff0c;前端请求后端服务一般的做法都是通过代理&…

SQL进阶技巧:如何删除第N次连续出现NULL值所存在的行?

目录 0 场景描述 1 数据准备 2 问题分析 问题拓展:如何删除第2次、第3次、第N次连续出现NULL值所在的行? 3 小结 0 场景描述 有下面的场景: 我们希望删除某id中连续存在NULL值的所有行,但是保留第一次出现不为NULL值的以下所有存在NULL值的行。具体如下图所示: 如…

Leetcode 判断子序列

通过双指针来判断字符串s是否是字符串t的子序列。 算法思想&#xff1a; 双指针法&#xff1a; 我们使用两个指针i和j分别遍历字符串s和t。初始时&#xff0c;i指向s的第一个字符&#xff0c;j指向t的第一个字符。 匹配字符&#xff1a; 每次比较s[i]和t[j]&#xff1a; 如果…

大数据治理-数据质量管理

目录 一、定义数据质量 1.1 数据质量的定义 1.2 数据质量的重要性 二、常见的数据质量问题 2.1 数据不准确 2.2 数据不完整 2.3 数据不一致 2.4 数据不及时 2.5 数据无效 2.6 数据重复 三、数据清洗与转换 3.1 数据清洗 3.1.1 数据审计 3.1.2 数据验证 3.1.3 数…

uniapp小程序自定义聚合点

注&#xff1a; 1.默认的聚合点可以点击自动展示子级点位&#xff0c;但是自定义的聚合点在ios上无法触发markerClusterClick的监听&#xff0c;至今未解决&#xff0c;不知啥原因 2.ios和安卓展示的点位样式还有有差别 源码附上 <template><view class"marke…

Linux - 环境变量 | 命令行参数 | 进程基础

文章目录 一、了解冯诺依曼体系结构1、概念2、对数据层面3、实例二、操作系统1、概念2、设计OS的目的3、定位4、操作系统怎么管理&#xff1f; 三、进程1、概念2、怎么管理进程3、描述进程-PCB4、描述进程怎么运行&#xff08;粗略&#xff09;5、进程属性6、创建子进程7、创建…

PDF文件为什么不能编辑是?是啥原因导致的,有何解决方法

PDF文件格式广泛应用于工作中&#xff0c;但有时候我们可能遇到无法编辑PDF文件的情况。这可能导致工作效率降低&#xff0c;特别是在需要修改文件内容时显得尤为棘手。遇到PDF不能编辑时&#xff0c;可以看看是否以下3个原因导致的。 一、文件受保护 有些PDF文件可能被设置了…

ChatGPT 现已登陆 Windows 平台

今天&#xff0c;OpenAI 宣布其人工智能聊天机器人平台 ChatGPT 已开始预览专用 Windows 应用程序。OpenAI 表示&#xff0c;该应用目前仅适用于 ChatGPT Plus、Team、Enterprise 和 Edu 用户&#xff0c;是一个早期版本&#xff0c;将在今年晚些时候推出"完整体验"。…

[每周一更]-(第119期):“BP”大揭秘:生物学与金融学中的微小单位竟有如此大不同!

最近&#xff08;2024.09.29&#xff09;央行要把存量房贷在LPR&#xff08;贷款市场报价利率&#xff09;基础上&#xff0c;降低30BP&#xff0c;刚好基因行业内&#xff0c;也有bp的概念&#xff0c;通过发音无法区分&#xff0c;以下就讲解下生物学的bp和金融学的BP的概念的…

汽车零部件行业CRM应用数字化解决方案解析

1.行业背景与挑战分析 近年来&#xff0c;随着国家对新能源汽车行业的大力支持&#xff0c;国内汽车产业不仅在国内市场实现了弯道超车&#xff0c;而且新能源汽车的海外出口也开拓了新的市场&#xff0c;为自主品牌的新能源战略贡献了新的增长点&#xff1b;这一迅猛发展的趋…

最新版快递小程序源码 独立版快递系统 附教程

内容目录 一、详细介绍二、效果展示1.部分代码2.效果图展示 三、学习资料下载 一、详细介绍 懂得都懂&#xff0c;现在电商平台退换货量大&#xff0c;快递需求量大&#xff0c;对接物流一个单子4块到6块之间 其中间是例如润 其余的 就不说了吧 互站上买的源码 分享一下 还有…

如何查看默认网关地址:详细步骤

在日常的网络配置与故障排查中&#xff0c;了解并正确查看默认网关地址是一项基础且至关重要的技能。默认网关是连接本地网络与外部网络&#xff08;如互联网&#xff09;的关键节点&#xff0c;它扮演着数据包转发的重要角色。无论是家庭网络、办公室网络还是更复杂的网络环境…

SSM框架学习(六、快速启动框架:SpringBoot3实战)

目录 一、SpringBoot3介绍 1.SpringBoot3简介 2.快速入门 3.入门总结 &#xff08;1&#xff09;Question1&#xff1a;为什么依赖不需要写版本&#xff1f; &#xff08;2&#xff09;Question2&#xff1a;启动器&#xff08;starter&#xff09;是什么&#xff1f; &a…

震惊!OpenAI突破性进展,清华天才联手破解扩散模型难题!

扩散模型很成功&#xff0c;但也有一块重大短板&#xff1a;采样速度非常慢&#xff0c;生成一个样本往往需要执行成百上千步采样。为此&#xff0c;研究社区已经提出了多种扩展蒸馏&#xff08;diffusion distillation&#xff09;技术&#xff0c;包括直接蒸馏、对抗蒸馏、渐…

如何将LiDAR坐标系下的3D点投影到相机2D图像上

将激光雷达点云投影到相机图像上做数据层的前融合&#xff0c;或者把激光雷达坐标系下标注的物体点云的3d bbox投影到相机图像上画出来&#xff0c;都需要做点云3D点坐标到图像像素坐标的转换计算&#xff0c;也就是LiDAR 3D坐标转像素坐标。 看了网上一些文章都存在有错误或者…

利用Llama3、CrewAI与Groq打造高效智能邮件客服系统

一、唠嗑 如果说AI的到来&#xff0c;哪个行业最有危机感&#xff0c;我觉得电商客服应该是榜上有名的。目前像淘宝、京东其实也是先用AI客服进行回复&#xff0c;客户不满意才使用人工客服&#xff0c;从而达到降本增效的目的。 而本次&#xff0c;就是使用 Llama3 CrewAI …