linux系统编程(六)

1、线程

一个进程可以包含多个线程,同一程序中的所有线程会独立执行相同的程序,共享同一份全局内存区域(初始化数据段、未初始化数据段、堆)。

同样是执行并行任务,用子进程会有一些限制,比如:

  • 进程间的信息难以共享,需要使用进程通信;
  • 调用fork创建进程的代价相对较高。

线程可以很好地解决上面的问题

  • 方便、快速地共享信息,只要把数据复制到共享变量中即可(全局/堆)中即可。但是要注意不要出现多个线程试图同时修改一份信息的情况;
  • 创建线程比创建进程要快10倍甚至更多;

要注意,虽然线程之间不会共享栈数据,但是线程栈都是驻留在同一虚拟地址空间的,所以可以通过指针共享对方栈中的数据。

以下是一些线程数据类型:

  • pthread_t:线程ID
  • pthread_mutex_t:互斥对象
  • pthread_mutexattr_t:互斥属性对象
  • pthread_cond_t:条件变量
  • pthread_condattr_t:条件变量
  • pthread_key_t:条件变量的属性对象
  • pthread_once_t:一次性初始化控制上下文
  • pthread_attr_t:线程的属性对象

在多线程程序中,每个线程都有自己的errno。

在linux平台上,编译调用了pthread api的程序时需要设置 -pthread。

我们可以使用pthread_create来创建线程:

#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start)(void *), void *arg);

新线程会调用带有参数arg的函数start开始执行。将参数arg声明未void*类型,表示可以将任意对象的指针传递给start函数,一般来说,arg指向一个全局或者堆变量,也可以是NULL。如果向start传递多个参数,可以将arg指向一个结构。

有如下方式可以中止线程的运行:

  • 线程start函数指向return语句并返回;
  • 线程调用pthread_exit;
  • 调用pthread_cancel取消线程;
  • 任意线程调用exit,或者主线程执行return,都会导致进程中的所有线程立即中止。

pthread_exit会中止调用线程,返回值可以由另一个线程调用pthread_join来获取。

#include <pthread.h>
void pthread_exit(void *retval);

调用pthread_exit相当于在start函数中执行return。和return不同的是,可以在start调用的任意函数中调用pthread_exit。返回值retval不应该分配于线程栈中,类似的,也不能在线程栈中分配start函数的返回值。

如果主线程调用了pthread_exit,而不是exit或者return,其他线程将继续执行。

线程可以通过pthread_self获取自己的线程ID:

#include <pthread.h>
pthread_t pthread_self(void);

pthread_join用于等待某个线程的中止,如果线程已经中止pthread_join会立即返回。

#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);

如果retval未一个非空指针,那么会将线程中止时的返回值拷贝到retval中。

如果线程未分离(detach),必须使用pthread_join来进行连接。如果没有进行连接,那么线程中止时会产生僵尸线程。

pthread_join和wait有点类似,不过也有区别,线程之间是平等的,所以任意线程都可以调用pthread_join来(连接)回收另一个线程,而wait只能由父进程调用。pthread_join只能连接特定线程ID,这么做的意图是线程只能连接它知道的线程。

默认情况下,线程是可以连接的(joinable),线程退出时,其他线程可以调用pthread_join获取返回状态。有的时候,系统希望线程终止时能够自动清理并移除,可以调用pthread_detach。

#include <pthread.h>
int pthread_detach(pthread_t thread);

线程也可以调用pthread_detach(pthread_self())自行分离。一旦线程处于分离状态,就不能调用pthread_join来获取状态了。不过其他线程调用了exit或者主线程执行了return,分离的线程也会收到影响。

以下是一个简单的线程示例:

#include <unistd.h>
#include <stdio.h>
#include <time.h>
#include <pthread.h>
#include <sys/syscall.h> // gettidstatic void *func(void* arg) {int cnt = *(int*)arg;for(int i = 0; i < cnt; i++) {time_t ti = time(NULL);printf("%s tid:%ld, index:%d, cnt:%d\n", ctime(&ti), syscall(SYS_gettid), i, cnt);sleep(1);}
}int main(int argc, char **argv) {pthread_t th;int cnt = 10;int ret = pthread_create(&th, NULL, func, &cnt);pthread_join(th, NULL);printf("pid:%d, tid:%ld, finish!\n", getpid(), syscall(SYS_gettid));return 0;
}

2、线程同步

线程能够使用全局变量来共享信息,不过要注意的是:必须确保多个线程不会同时修改同一变量,或者说是某一线程不会读取正由其他线程修改的变量。临界区:指的是访问某个共享资源的代码片段,并且这段代码的执行应该为原子操作。

互斥量有两种状态:已锁定(locked)和未锁定(unlocked)。任何时候,至多只有一个线程可以锁定互斥量,试图对某个已经锁定的互斥量再次加锁,可能会阻塞线程,或者是报错失败。

线程锁定互斥量就会称为互斥量的所有者,只有所有者才能给互斥量解锁。获取(acquire)等于加锁和释放(release)等于解锁。

创建互斥量后,使用前必须要先初始化:

static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;

PTHREAD_MUTEX_INITIALIZER只能初始化静态分配的互斥量。

如果互斥量是不是静态分配的,那么需要调用pthread_mutex_init初始化:

#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);

如果attr为NULL,互斥量使用默认值。以下几种情况需要用该函数进行初始化:

  • 动态分配于堆中的互斥量;
  • 栈中分配的自动变量;
  • 初始化经由静态分配,且不使用默认属性的互斥量。

如果不需要使用自动/动态分配的互斥量时,应该调用pthread_mutex_destroy进行销毁。使用PTHREAD_MUTEX_INITIALIZER初始化的静态互斥量不需要销毁。

int pthread_mutex_destroy(pthread_mutex_t *mutex);

初始化之后,互斥量处于未锁定状态,我们可以用以下函数来进行加锁解锁:

#include <pthread.h>int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);

调用pthread_mutex_lock时如果互斥量处于未锁定状态,调用之后立即返回。如果有其他线程已经锁定了互斥量,那么调用pthread_mutex_lock的一方将会一直阻塞直到互斥量倍解锁。

避免死锁问题,最简单的方法是定义互斥量的层级关系。多个线程对一组互斥量操作时,应该总是以相同的顺序对该组互斥量进行锁定。

条件变量也有动态和静态分配之分,静态分配如下:

static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

动态分配的条件变量需要使用pthread_cond_init来初始化,它和mutex类似。

#include <pthread.h>
int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);

条件变量使用完成后要调用destroy

#include <pthread.h>
int pthread_cond_destroy(pthread_cond_t *cond);

条件变量的主要操作是发送信号(signal)和等待(wait)。发送信号操作指的是通知一个或多个处于等待状态的线程,某个共享变量的状态已经改变。等待操作指的是收到一个通知之前一直处于阻塞状态。

#include <pthread.h>
int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);

pthread_cond_wait用于阻塞线程直至收到条件变量cond的通知。pthread_cond_signal和pthread_cond_broadcast用于向条件变量发出信号。signal只保证唤醒至少一条被阻塞的线程,broadcast会唤醒所有线程。

pthread_cond_signal适用于线程在执行完全相同的任务,pthread_cond_broadcast适用于线程在执行不同的任务(关于条件变量的判断条件不同)。

#include <pthread.h>
int pthread_cond_timewait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime);

pthread_cond_timewait和pthread_cond_wait几乎相同,不过pthread_cond_timewait可以设置阻塞时间的上限。

必须是由一个while循环,而不是if语句,来控制堆pthread_cond_wait的调用。这是因为,pthread_cond_wait返回时,不能确定判断条件的状态,应该立即重新检查判断条件,不满足的情况下继续休眠。

取消线程:

#include <pthread.h>
int pthread_cancel(pthread_t thread);

pthread_cancel用于向线程发送一个请求,要求其立刻退出,发出请求后,pthread_cancel立即返回,不会等待目标线程退出。如果线程的取消状态和类型分别为启动和延迟,只有在线程达到某个取消点时,取消请求才会起作用。

3、线程池

以下是一个简单的线程池

#include <unistd.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>typedef struct task {void (*func)(void*);void *arg;
} task_t;typedef struct threadpool {pthread_t 			*threads;task_t 				*task_queue;int 				queue_capacity;int 				queue_size;int 				thread_count;int 				running;pthread_mutex_t 	mutex;pthread_cond_t 		not_empty;pthread_cond_t 		not_full;
} threadpool_t;static void *threadpool_worker(void *arg) {threadpool_t *pool = (threadpool_t *)arg;task_t task;while(1) {pthread_mutex_lock(&pool->mutex);while(pool->queue_size == 0 && pool->running) {pthread_cond_wait(&pool->not_empty, &pool->mutex);}if(!pool->running) {pthread_mutex_unlock(&pool->mutex);pthread_exit(NULL);}task = pool->task_queue[0];for(int i = 0; i < pool->queue_size - 1; i++)pool->task_queue[i] = pool->task_queue[i+1];pool->queue_size--;pthread_cond_signal(&pool->not_full);pthread_mutex_unlock(&pool->mutex);task.func(task.arg);}return NULL;
}threadpool_t *threadpool_create(int thread_count, int queue_capacity) {threadpool_t *pool		 = (threadpool_t *)malloc(sizeof(threadpool_t));pool->thread_count 		 = thread_count;pool->queue_capacity 	 = queue_capacity;pool->queue_size 		 = 0;pool->running			 = 1;pool->threads			 = (pthread_t *)malloc(thread_count * sizeof(pthread_t));pool->task_queue		 = (task_t *)malloc(queue_capacity * sizeof(task_t));int ret = pthread_mutex_init(&pool->mutex, NULL);pthread_cond_init(&pool->not_empty, NULL);pthread_cond_init(&pool->not_full, NULL);for(int i = 0; i < thread_count; i++) {pthread_create(&pool->threads[i], NULL, threadpool_worker, (void *)pool);}#ifdef DEBUGprintf("threadpool create OK! ");for(int i = 0; i < pool->thread_count; i++) {printf("%ld ", pool->threads[i]);}printf("\n");
#endifreturn pool;
}void threadpool_destroy(threadpool_t *pool) {pool->running = 0;pthread_cond_broadcast(&pool->not_empty);pthread_cond_broadcast(&pool->not_full);for(int i = 0; i < pool->thread_count; i++) {pthread_join(pool->threads[i], NULL);}pthread_mutex_destroy(&pool->mutex);pthread_cond_destroy(&pool->not_empty);pthread_cond_destroy(&pool->not_full);free(pool->threads);free(pool->task_queue);free(pool);
}int threadpool_addTask(threadpool_t *pool, void (*func)(void *), void *arg) {
#ifdef DEBUGprintf("pool pointer in main before addTask: %p\n", pool);
#endifpthread_mutex_lock(&pool->mutex);while(pool->queue_size == pool->queue_capacity && pool->running) {pthread_cond_wait(&pool->not_full, &pool->mutex);}if(!pool->running) {pthread_mutex_unlock(&pool->mutex);return -1;}int index = pool->queue_size;
#ifdef DEBUGprintf("current index:%d\n", index);
#endiftask_t *task = &pool->task_queue[index];task->func = func;task->arg = arg;pool->queue_size++;
#ifdef DEBUGprintf("add task OK!\n");
#endifpthread_cond_signal(&pool->not_empty);pthread_mutex_unlock(&pool->mutex);return index; 
}void print_task(void *args) {int num = *(int *)args;for(int i = 0; i < num; i++) {printf("thread:%ld num:%d index:%d\n", pthread_self(), num, i);sleep(1);}
}int main(int argc, char **argv) {threadpool_t *pool = threadpool_create(3, 10);int num[20];for(int i = 0; i < 20; i++) {num[i] = i+1;int index = threadpool_addTask(pool, print_task, &num[i]);
#ifdef DEBUGprintf("addTask:%d index:%d\n", i, index);
#endif}sleep(30);printf("stop!\n");threadpool_destroy(pool);return 0;
}

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

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

相关文章

Java设计模式 —— 【结构型模式】外观模式详解

文章目录 概述结构案例实现优缺点 概述 外观模式又名门面模式&#xff0c;是一种通过为多个复杂的子系统提供一个一致的接口&#xff0c;而使这些子系统更加容易被访问的模式。该模式对外有一个统一接口&#xff0c;外部应用程序不用关心内部子系统的具体的细节&#xff0c;这…

基于Springboot + vue实现的汽车资讯网站

&#x1f942;(❁◡❁)您的点赞&#x1f44d;➕评论&#x1f4dd;➕收藏⭐是作者创作的最大动力&#x1f91e; &#x1f496;&#x1f4d5;&#x1f389;&#x1f525; 支持我&#xff1a;点赞&#x1f44d;收藏⭐️留言&#x1f4dd;欢迎留言讨论 &#x1f525;&#x1f525;&…

Html:点击图标链接发起QQ临时会话

我们在做前端开发的时候&#xff0c;会遇到用户需要点击一个图标可以发起QQ临时会话&#xff0c;这样不用添加好友也能沟通的&#xff0c;那我们就来看看如何实现这个功能&#xff1a; <a href"http://wpa.qq.com/msgrd?v3&uin你的QQ号码&siteqq&menuyes…

【后端面试总结】MySQL主从复制逻辑的技术介绍

MySQL主从复制逻辑的技术介绍 1. 基本概念 MySQL主从复制是一种数据库复制技术&#xff0c;用于将一个数据库服务器&#xff08;主服务器&#xff0c;Master&#xff09;上的数据更改同步到一个或多个其他数据库服务器&#xff08;从服务器&#xff0c;Slave&#xff09;上。…

echarts画风向杆

1.安装echarts 2.引入echarts 4.获取数据&#xff0c;转换数据格式 windProfile.title.text ${moment(time.searchTime[0], ‘YYYY-MM-DD HH:mm:ss’).format( ‘YYYY-MM-DD HH:mm’ )}-${moment(time.searchTime[1], ‘YYYY-MM-DD HH:mm:ss’).format(‘YYYY-MM-DD HH:mm’)…

Linux系统编程——理解系统内核中的信号捕获

目录 一、sigaction() 使用 信号捕捉技巧 二、可重入函数 三、volatile关键字 四、SIGCHLD信号 在信号这一篇中我们已经学习到了一种信号捕捉的调用接口&#xff1a;signal(),为了深入理解操作系统内核中的信号捕获机制&#xff0c;我们今天再来看一个接口&#xff1a;si…

HTTP、HTTPS和SOCKS5代理協議

一、HTTP代理協議 HTTP代理是最基礎、使用最廣泛的代理協議之一。它主要用於處理HTTP協議的數據請求&#xff0c;即流覽網頁時的數據傳輸。HTTP代理的工作原理相對簡單&#xff1a;當用戶向網站發送請求時&#xff0c;HTTP代理伺服器首先接收該請求&#xff0c;然後將其轉發給…

IEC104 协议 | 规约帧格式 / 规约调试

注&#xff1a;本文为 “ IEC104 协议” 相关文章合辑。 未整理去重&#xff0c;如有内容异常请看原文。 图片清晰度限于引文原状。 从零开始理解 IEC104 协议之一 ——104 规约帧格式 洪城小电工 IP 属地&#xff1a;江西 2020.06.10 00:30:54 前言 本文根据相关标准、本…

代码随想录训练营第二十七天| 贪心理论基础 455.分发饼干 376. 摆动序列 53. 最大子序和

贪心没有套路&#xff0c;说白了就是常识性推导加上举反例 今天的内容比较简单 简单了解贪心是通过局部最优解反推全局最优解&#xff08;有经验成分&#xff09; 455.分发饼干 题目链接&#xff1a;455. 分发饼干 - 力扣&#xff08;LeetCode&#xff09; 讲解链接&#xff…

WPS如何快速将数字金额批量转换成中文大写金额,其实非常简单

大家好&#xff0c;我是小鱼。 在日常的工作中经常会遇到需要使用金额大写的情况&#xff0c;比如说签订业务合同时一般都会标注大写金额&#xff0c;这样是为了安全和防止串改。但是很多人也许不太熟悉金额大写的方法和习惯&#xff0c;其它没有关系&#xff0c;我们在用WPS制…

Vue3之Pinia

在前端开发中&#xff0c;随着应用程序的规模和复杂性增加&#xff0c;状态管理成为了不可或缺的一部分。Pinia作为Vue 3的专属状态管理库&#xff0c;以其轻量级、易用性和灵活性&#xff0c;逐渐成为了Vue开发者的新宠。本文将详细介绍Pinia的基本概念、功能特点、应用场景、…

MHA binlog server

GTID模式下切换的时候&#xff0c;默认是不会去Master上获取binlog&#xff0c;如果配置了Binlog Server&#xff0c;MHA机会去binlog-server上获取&#xff0c;下面创建一个binlog server&#xff0c;并验证binlog server能够在主库binlog被清理的情况下提供日志恢复. 搭建bi…

针对超大规模病理图像分析!华中科技大学提出医学图像分割模型,提高干燥综合征诊断准确性

口干、眼干、皮肤干&#xff0c;每天伴有不明原因的肌肉酸痛和全身乏力&#xff0c;如果以上症状你「中招」了&#xff0c;除了考虑冬季天气干燥外&#xff0c;还应该警惕一种常见却总是被我们忽视的疾病——干燥综合征 (Sjgren’s Syndrome, SS)。 干燥综合征是以外分泌腺高度…

本地部署 LLaMA-Factory

本地部署 LLaMA-Factory 1. 本地部署 LLaMA-Factory2. 下载模型3. 微调模型3-1. 下载数据集3-2. 配置参数3-3. 启动微调3-4. 模型评估3-5. 模型对话 1. 本地部署 LLaMA-Factory 下载代码&#xff0c; git clone https://github.com/hiyouga/LLaMA-Factory.git cd LLaMA-Facto…

GPU环境配置

ubuntu基本环境配置 1. 更新包列表 sudo apt-get update sudo apt upgrade 2. 安装英伟达显卡驱动 使用wget在命令行下载驱动包 wget https://cn.download.nvidia.com/XFree86/Linux-x86_64/550.100/NVIDIA-Linux-x86_64-550.100.run 上面使用的是4090版本的&#xff0c;…

[创业之路-199]:《华为战略管理法-DSTE实战体系》- 3 - 价值转移理论与利润区理论

目录 一、价值转移理论 1.1. 什么是价值&#xff1f; 1.2. 什么价值创造 &#xff08;1&#xff09;、定义 &#xff08;2&#xff09;、影响价值创造的因素 &#xff08;3&#xff09;、价值创造的三个过程 &#xff08;4&#xff09;、价值创造的实践 &#xff08;5&…

ASP.NET |日常开发中定时任务详解

ASP.NET &#xff5c;日常开发中定时任务详解 前言一、定时任务的概念与用途1.1 定义1.2 应用场景 二、在ASP.NET中实现定时任务的方式2.1 使用System.Timers.Timer2.2 使用Quartz.NET 三、定时任务的部署与管理3.1 部署考虑因素3.2 管理与监控 结束语优质源码分享 ASP.NET &am…

安装openGauss数据库一主一备

安装openGauss数据库一主一备 一.安装准备1.修改/etc/selinux/config文件中的“SELINUX”值为“disabled”。2.重新启动操作系统。3.检查防火墙是否关闭。4.关闭防火墙并禁止开机重启。5.关闭交换内存6.设置主机名7.设置解析主机名和 IP 地址8.安装相关依赖包9.创建单独的用户组…

【unity】【游戏开发】Unity项目一运行就蓝屏报Watch Dog Timeout

【背景】 由于是蓝屏所以没法截屏&#xff0c;总之今天遇到了一开Unity&#xff0c;过一阵就蓝屏的情况&#xff0c;报Watch Dog Timeout。 【分析】 通过任务管理器查看&#xff0c;发现Unity占用率100%&#xff0c;再观察Unity内部&#xff0c;每次右下角出现一个Global I…

如何从 0 到 1 ,打造全新一代分布式数据架构

导读&#xff1a;本文从 DIKW&#xff08;数据、信息、知识、智慧&#xff09; 模型视角出发&#xff0c;探讨数字世界中数据的重要性问题。接着站在业务视角&#xff0c;讨论了在不断满足业务诉求&#xff08;特别是 AI 需求&#xff09;的过程中&#xff0c;数据系统是如何一…