当前位置: 首页 > news >正文

LinuxC语言并发程序笔记(第二十一天) - 实践

线程模型:

我们前面学过,进程是是一个程序所分配的资源,是程序执行一次的过程,其参与内核的调用,并且互不影响。在Linux系统里,每当有进程被创建,系统就会为其创建一个叫task_sturck的存储空间(类似文件夹这种,方便管理和操控)。

但是进程与进程是不共用一个存储空间的,因此在系统每次调用程序的时候创建进程的时候,都需要把那个程序所处的存储空间复制一遍到高速缓存(cache)中,但是高速缓存的空间有限,往往只能存储一两个程序的资源,那我们在进程和进程切换的时候,高速缓存就要不停的洗数据、存数据,因此系统会开销会很大。

所以系统引入了轻量级进程LWP,也就是线程。

同一个进程里的不同线程都是共用一个存储空间,这样系统调用的时候就可以让高速缓存直接调用当前的存储空间的数据,切换的时候直接在这个数据中找对应的程序就可以了。

(ts:Linux系统是不区分线程和进程的,对于系统来说,两者是几乎一样的对象,所以线程也会被系统包成task_sturck。)

Linux线程库:

linux线程库是<pthread.h>。

线程的创建:

通过库函数

int pthread_create(pthread_t *thread,const pthread_arr_t *arr,void *(*routine)(void *),void *arg);

成功返回0,失败返回错误码,其中:
pthread_t 是 pthread 库定义的变量,thread 是线程对象。
attr是线程属性,写NULL就是默认属性。
routine是指针函数,线程执行的函数。
arg就是和进程一样的,传给线程的参数文件。

线程参数有:

属性名作用相关函数
detach state设置线程是否为分离线程(detached),分离线程结束后资源自动回收,不需要 pthread_joinpthread_attr_setdetachstate()
stack size设置线程栈大小(单位:字节)。pthread_attr_setstacksize()
stack address设置线程栈的起始地址(一般不推荐手动设置)。pthread_attr_setstackaddr()
guard size设置栈保护区大小(防止栈溢出)。pthread_attr_setguardsize()
inherit scheduler设置是否继承父线程的调度策略。pthread_attr_setinheritsched()
scheduling policy设置线程调度策略(如 SCHED_FIFOSCHED_RRSCHED_OTHER)。pthread_attr_setschedpolicy()
scheduling priority设置线程调度优先级(需与调度策略配合使用)。pthread_attr_setschedparam()
scope设置线程竞争范围(PTHREAD_SCOPE_SYSTEM 或 PTHREAD_SCOPE_PROCESS)。pthread_attr_setscope()

使用:

pthread_attr_t attr;
// 初始化属性对象
pthread_attr_init(&attr);
// 设置为分离线程
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
// 设置栈大小为 1MB
pthread_attr_setstacksize(&attr, 1024 * 1024);

线程回收:

int pthread_join(pthread_t thread,void **retval);

成功返回0,失败返回错误码,其中:
thread 是要回收的线程对象(!!!不是指针,不是传地址)。
调用此函数的时候,主程序会一直阻塞直至线程结束。
retval 是线程的返回值,一个二级指针,使用需要实现创建一个指针,然后把地址传进函数即可。

线程的结束:

void pthread_exit(void *retval);

结束当前的前程,不能用exit,因为所有线程共用一个空间,关闭会一起关。
retval的值可以被其他线程用pthread_join接收(如主线程)。

例子:

#include 
#include 
#include 
#include 
#include 
char message[32] = "Hello World!";
void *thread_func(void *arg);
int main(){pthread_t a_thread;void *result;if(pthread_create(&a_thread,NULL,thread_func,NULL) != 0){perror("pthread_create");exit(-1);}//会一直阻塞直到a_thread结束pthread_join(a_thread,&result);printf("result is %s\n",(char *)result);printf("message is %s\n",message);return 0;
}
void *thread_func(void *arg){sleep(1);strcpy(message,"maked by thread");pthread_exit("Thank you for waiting for me");
}

编译的时候要
gcc -o test test.c -lpthread
因为pthread属于第三方库。

线程间通信:

我们前面都说了,同一个进程里的不同线程都是共用一个存储空间,所以他们直接的通信会简单很多——通过全局变量进行交换数据,但是,代码中间怎么知道那些是给自己的呢,哪些适合要自己写东西给别人,靠的就是同步和互斥的机制。(互斥后面再说)

同步:

同步不是我们经常说的一起做,而是多个任务按照约定的先后顺序,配合完成一件事情。
在1968年,Edsger Dijkstra 基于信息量的概念提出了一种同步机制,由信息量决定程序的继续运行还是阻塞。

信息量:

其代表一类资源,其值表示系统中该资源的数量。

信息量是一个受保护的变量,只能通过以下三种操作访问:
1、初始化
2、P操作(申请资源)
3、V操作(释放资源)

P(s)操作表示:
如果信号量s大于0,申请资源的任务继续,s--;
如果等于0,就阻塞。

V(s)操作表示:
s++;
如果有在等资源的任务,即P(s)阻塞的,会唤醒它,让它继续工作。

信号量Posix有两种:
1、无名信号量(基于内存的)
2、有名信号量(基于储存的)
前者用于线程间,后者线程进程都可。

信号量的库函数:

其库函数在头文件<semaphore.h>中

信号量初始化:

int sem_init(sem_t *sem,int pshared,unsigned int val);

成功返回0,失败返回EOF,其中:
sem_t是库中定义的数据,sem是信号量对象。
pshared 0表示在线程间 , 1表示在进程间。
val是信号量的初值。

信号量的操作:

int sem_wait(sem_t *sem);   p操作
int sem_post(sem_t *sem);  v操作

成功返回0,失败返回EOF。
记忆小技巧,post含p所以不是p操作。wait等待,借走了资源让别人等待。

例子:

#include 
#include 
#include 
#include 
#include 
char message[32] = "Hello World!";
void *thread_func(void *arg);
int main(){pthread_t a_thread;void *result;if(pthread_create(&a_thread,NULL,thread_func,NULL) != 0){perror("pthread_create");exit(-1);}//会一直阻塞直到a_thread结束pthread_join(a_thread,&result);printf("result is %s\n",(char *)result);printf("message is %s\n",message);return 0;
}
void *thread_func(void *arg){sleep(1);strcpy(message,"maked by thread");pthread_exit("Thank you for waiting for me");
}

我们在启动程序的时候可以使用第一节课学的 ps aux -L | grep test查看这两个线程


这两个进制都处于等待状态。

代码解析:

定义了两个信号量r和w,主线程负责写入数据,创建的线程负责读出写入的长度。
w初始为1,主线程P操作使其减一,是为了防止无限制写入数据导致子进程还没读出就改变了缓冲区,在写入数据后V操作r使其加一。
处于阻塞的子线工作,但为了防止一直执行函数,P操作使r减一,读取后给wV操作,让主线程继续工作。
以此循环。

类比:

其实同步有点像小孩吃糖,a给b一颗糖,b吃完后给a一颗,a吃完再给b......没糖的话两个小孩都不会动,同个时间只能有一颗糖。

http://www.jsqmd.com/news/133821/

相关文章:

  • 灵足之脑:大模型驱动双足机器人全栈技术实战系列》第 3 篇:大模型简史 —— 从 Transformer 到多模态,大脑是如何准备好的?
  • Danbooru批量图片采集实战指南:从入门到精通
  • wamp环境如何使用composer_WampServer环境下配置和运行composer的教程
  • 【Open-AutoGLM邀请码获取指南】:3步教你成功注册内测账号
  • 成都恒利泰国产替代LTCC低通滤波器
  • 跨平台直播聚合开发指南:构建多源直播应用实战
  • 电磁定则复习
  • Obsidian全功能日历插件:终极时间管理解决方案
  • 2025年深圳大型活动与年会场地专业推荐:精选五大特色场地解决您的选址难题 - 品牌2026
  • java springboot基于微信小程序的手机银行系统(源码+文档+运行视频+讲解视频)
  • 2025年AI营销获客系统代理公司推荐,讯灵Ai的代理电话是什么 - myqiye
  • B站抽奖神器BiliRaffle:告别手动统计,3分钟开启专业级抽奖活动
  • Spotify音乐离线下载神器:打造永久个人音乐库
  • 2025年火电厂脱硫塔直销厂家权威推荐榜单:废气玻璃钢脱硫塔/玻璃钢喷淋净化塔/锅炉玻璃钢脱硫塔源头厂家精选 - 品牌推荐官
  • 2025年专业的精密零件加工厂家推荐,推荐精密零件制造商解析 - 工业品牌热点
  • YACReader完全手册:打造个人数字漫画图书馆的终极方案
  • AIGC与CI/CD的深度融合:在流水线中嵌入AI测试评估节点
  • 低代码配置、可落地、业务赋能:数据分类分级系统引领政务数据治理新实践
  • 语音合成新纪元:GPT-SoVITS实现高自然度音色克隆
  • Open-AutoGLM开源组件详解(仅限高级开发者访问的内部文档流出)
  • 7-Zip ZS智能压缩:多算法高效文件管理新体验
  • GPT-SoVITS模型冷启动问题解决方案
  • 如何快速部署Stable Diffusion:Docker容器化完整指南
  • 2025年终数字化采购平台行业实践观察解析:技术赋能采购全链路协同升级 - 深度智识库
  • 为什么开发者都在关注GPT-SoVITS?真相揭秘
  • GPT-SoVITS在语音社交平台的内容创作赋能
  • 终极Mac窗口管理方案:一键实现桌面高效布局
  • 如何用Oni-Duplicity快速定制你的《缺氧》游戏体验?7步终极指南
  • 2025年最新智能辅助评标系统行业实践白皮书:技术赋能评审效率与公平性提升 - 深度智识库
  • RSSHub-Radar智能订阅指南:打造你的专属信息获取系统