linux系统编程---线程总结

线程总结

  • 1 线程的实现
    • 线程创建
    • 线程退出
    • 线程等待
    • 线程清理
  • 2 线程的属性
    • 线程的分离
    • 线程的栈地址
    • 线程栈大小
    • 线程的调度策略
    • 线程优先级
  • 3 线程的同步
    • 互斥锁
    • 读写锁
    • 条件变量
    • 信号量

线程是系统独立调度和分配的基本单位。同一进程中的多个线程将共享该进程中的全部系统资源,例如文件描述符和信号处理等。一个进程可以有很多线程,每个线程并发执行不同的任务。

1 线程的实现

线程创建

int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);
  • thread:指向线程标识符的指针,当线程创建成功后,用来返回创建的线程ID
  • att:指定线程的属性,NULL表示默认
  • start_routine:函数指针,指向线程创建后要调用的函数,直接赋值函数名即可
  • arg是传给函数的参数
#include  <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>void * my_pthread(void *arg)
{printf("pthread id =%ld\n",pthread_self());return NULL;
}int main(void)
{pthread_t pid;if(pthread_create(&pid,NULL,my_pthread,NULL)){printf("pthread_create error\n");exit(1);}sleep(2);return 0;
}

在这里插入图片描述

线程退出

void pthread_exit(void *retval);
  • retval是线程结束时的返回值,可由其他函数如pthread_join来获取

注意:如果进程中任何一个线程调用exit()或_exit()函数,那么整个进程就会终止。线程的正常退出方法有线程从线程函数中返回(return)、线程可以被另一个线程终止以及线程自己调用pthread_exit()

线程等待

在调用pthread_create函数后,就会运行相关的线程函数。pthread_join()是一个线程阻塞函数,调用后,调用者一直等待指定的线程结束才返回,被等待线程的资源就会被回收。

int pthread_join(pthread_t thread, void **retval);
  • pthread是等待结束的线程id
  • retval是用户定义的指针,用来存储被等待线程结束时的返回值。
#include  <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>void * my_pthread(void *arg)
{int ret =5;printf("pthread id =%ld\n",pthread_self());pthread_exit((void *)&ret);
}int main(void)
{pthread_t pid;void *ret ;if(pthread_create(&pid,NULL,my_pthread,NULL)){printf("pthread_create error\n");exit(1);}pthread_join(pid,&ret);printf("ret = %d\n",*(int * )ret);return 0;
}

线程函数运行结束是可以有返回值的,这个函数的返回值怎么返回呢?可以通过return语句进行返回,也可以通过pthread_exit()函数进行返回。函数的这个返回值怎么来接收呢?就通过pthread_join()函数来接受。
当然也可以选择不接受该线程的返回值,只阻塞该线程:

pthread_join(tid, NULL);

线程清理

线程终止有两种情况:正常终止和非正常终止。线程主动调用pthread_exit()或者从线程中return都使进程正常退出。非正常终止时线程在其他线程的干预下停止运行或者由于自身运行错误而退出。

线程可以安排它退出时需要调用的函数,这与进程在退出时可以用atexit函数安排退出函数是类似的。这样的函数称为线程清理处理程序。一个线程可以建立多个清理处理程序。

void pthread_cleanup_push(void (* rtn)(void *), void * arg);

函数说明:将清除函数压入清除栈。rtn是清除函数,arg是清除函数的参数。

void pthread_cleanup_pop(int execute);

函数说明:将清除函数弹出清除栈。执行到pthread_cleanup_pop()时,参数execute决定是否在弹出清除函数的同时执行该函数,execute非0时,执行;execute为0时,不执行。

从pthread_cleanup_push的调用点到pthread_cleanip_pop之间的程序段中的终止动作(包括调用pthread_exit()和异常终止,不包括return)都将执行pthread_cleanup_push()所指定的清理函数。

int pthread_cancel(pthread_t thread);

函数说明:取消线程,该函数在其他线程中调用,用来强行杀死指定的线程。

2 线程的属性

参考文章:

  1. https://blog.csdn.net/zsf8701/article/details/7842392
  2. https://blog.csdn.net/qq_22847457/article/details/89461222
  3. https://blog.csdn.net/yychuyu/article/details/84503261?spm=1001.2101.3001.6650.1&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-1.pc_relevant_antiscanv2&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-1.pc_relevant_antiscanv2&utm_relevant_index=2

线程属性标识符:pthread_attr_t 包含在 pthread.h 头文件中。

typedef struct
{int                   detachstate;      //线程的分离状态int                   schedpolicy;     //线程调度策略structsched_param     schedparam;      //线程的调度优先级int                   inheritsched;    //线程的继承性int                   scope;           //线程的作用域size_t                guardsize;       //线程栈末尾的警戒缓冲区大小int                   stackaddr_set;   //线程的栈设置void*                 stackaddr;       //线程栈的位置size_t                stacksize;       //线程栈的大小
}pthread_attr_t;

属性值不能直接设置,须使用相关函数进行操作,初始化的函数为pthread_attr_init,这个函数必须在pthread_create函数之前调用。之后须用pthread_attr_destroy函数来释放资源。线程属性主要包括如下属性:作用域(scope)、栈尺寸(stack size)、栈地址(stack address)、优先级(priority)、分离的状态(detached state)、调度策略和参数(scheduling policy and parameters)。默认的属性为非绑定、非分离、缺省1M的堆栈、与父进程同样级别的优先级。

线程的分离

分离状态属性确定使用线程属性对象 attr 创建的线程是在可连接状态还是可分离状态下创建。

如果在创建线程时就知道不需要了解线程的终止状态,就可以修改pthread_attr_t结构中的detachstate线程属性,让线程一开始就处于分离状态。可以使用pthread_attr_setdetachstate函数把线程属性detachstate设置成以下两个合法值之一:

PTHREAD_CREATE_DETACHED
PTHREAD_CREATE_JOINABLE
		/* 设置线程的分离状态 */int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);/* 获取线程的分离状态 */	int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate);

注意事项:如果设置一个线程为分离线程,而这个线程运行又非常快,它很可能在pthread_create函数返回之前就终止了,

它终止以后就可能将线程号和系统资源移交给其他的线程使用,这样调用pthread_create的线程就得到了错误的线程号。

解决方法:要避免这种情况可以采取一定的同步措施,最简单的方法之一是可以在被创建的线程里调用pthread_cond_timedwait函数,让这个线程等待一会儿,留出足够的时间让函数pthread_create返回。设置一段等待时间,是在多线程编程里常用的方法。

线程的栈地址

POSIX.1定义了两个常量_POSIX_THREAD_ATTR_STACKADDR 和_POSIX_THREAD_ATTR_STACKSIZE检测系统是否支持栈属性。也可以给sysconf函数传递_SC_THREAD_ATTR_STACKADDR或 _SC_THREAD_ATTR_STACKSIZE来进行检测。

当进程栈地址空间不够用时,指定新建线程使用由malloc分配的空间作为自己的栈空间。通过pthread_attr_setstack和pthread_attr_getstack两个函数分别设置和获取线程的栈地址。

int pthread_attr_setstack(pthread_attr_t *attr,void *stackaddr, size_t stacksize);int pthread_attr_getstack(const pthread_attr_t *attr,void **stackaddr, size_t *stacksize);

线程栈大小

当系统中有很多线程时,可能需要减小每个线程栈的默认大小,防止进程的地址空间不够用。当线程调用的函数会分配很大的局部变量或者函数调用层次很深时,可能需要增大线程栈的默认大小。

函数pthread_attr_getstacksize和 pthread_attr_setstacksize可以设置或者获取线程的栈大小。

       int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);int pthread_attr_getstacksize(const pthread_attr_t *attr, size_t *stacksize);

线程的调度策略

POSIX标准指定了三种调度策略:先入先出策略 (SCHED_FIFO)、循环策略 (SCHED_RR) 和自定义策略 (SCHED_OTHER)。SCHED_FIFO 是基于队列的调度程序,对于每个优先级都会使用不同的队列。SCHED_RR 与 FIFO 相似,不同的是前者的每个线程都有一个执行时间配额。SCHED_FIFO 和 SCHED_RR 是对 POSIX Realtime 的扩展。SCHED_OTHER 是缺省的调度策略。

  1. 新线程默认使用 SCHED_OTHER 调度策略。线程一旦开始运行,直到被抢占或者直到线程阻塞或停止为止。
  2. SCHED_FIFO
    如果调用进程具有有效的用户 ID ,则争用范围为系统 (PTHREAD_SCOPE_SYSTEM) 的先入先出线程属于实时 (RT) 调度类。如果这些线程未被优先级更高的线程抢占,则会继续处理该线程,直到该线程放弃或阻塞为止。对于具有进程争用范围 (PTHREAD_SCOPE_PROCESS)) 的线程或其调用进程没有有效用户 ID 的线程,请使用 SCHED_FIFO,SCHED_FIFO 基于 TS 调度类。
  3. SCHED_RR
    如果调用进程具有有效的用户 ID ,则争用范围为系统 (PTHREAD_SCOPE_SYSTEM)) 的循环线程属于实时 (RT) 调度类。如果这些线程未被优先级更高的线程抢占,并且这些线程没有放弃或阻塞,则在系统确定的时间段内将一直执行这些线程。对于具有进程争用范围 (PTHREAD_SCOPE_PROCESS) 的线程,请使用 SCHED_RR(基于 TS 调度类)。此外,这些线程的调用进程没有有效的用户 ID 。
      int pthread_attr_setschedpolicy(pthread_attr_t *attr, int policy);int pthread_attr_getschedpolicy(const pthread_attr_t *attr, int *policy);

参数policy可以为SCHED_FIFO, SCHED_RR, and SCHED_OTHER

线程优先级

线程优先级存放在结构sched_param中,

struct sched_param {int sched_priority;     /* Scheduling priority */};

可以通过pthread_attr_setschedparam()和pthread_attr_getschedparam设置和获取线程的调度优先级 ,它的完整定义是:

       int pthread_attr_setschedparam(pthread_attr_t *attr,const struct sched_param *param);int pthread_attr_getschedparam(const pthread_attr_t *attr,struct sched_param *param);

3 线程的同步

参考文章

https://blog.csdn.net/qq_43412060/article/details/106989170
https://www.cnblogs.com/wsw-seu/p/8036218.html

互斥锁

互斥锁用pthread_mutex_t数据类型表示,互斥锁可以用来控制线程对共享资源的互斥访问,确保同一时间只有一个线程访问数据 。

/* 初始化互斥锁 */
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr);
/* 销毁互斥锁 */
int pthread_mutex_destroy(pthread_mutex_t *mutex);
/* 对互斥锁加锁 */
int pthread_mutex_lock(pthread_mutex_t *mutex);
/* 对互斥锁尝试加锁 */
int pthread_mutex_trylock(pthread_mutex_t *mutex);
/* 对互斥锁解锁 */
int pthread_mutex_unlock(pthread_mutex_t *mutex);

接下来我们来实现主线程负责接收用户输入,函数线程负责将用户输入打印到终端界面

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<string.h>
#include<unistd.h>#include<pthread.h>
#include<time.h>
#include<fcntl.h>pthread_mutex_t mutex;char buff[128] = {0};void *fun(void *arg)
{while(1){pthread_mutex_lock(&mutex);if(strncmp(buff,"end",3) == 0){break;}printf("fun :%s\n",buff);memset(buff,0,128);int n = rand() % 3 +1;sleep(n);pthread_mutex_unlock(&mutex);n = rand() % 3 + 1;sleep(n);}
}int main()
{srand((unsigned int)(time(NULL) * time(NULL)));pthread_mutex_init(&mutex,NULL);//初始化的锁是解锁状态的//创建一个线程pthread_t id;int res = pthread_create(&id,NULL,fun,NULL);assert(res == 0);while(1){pthread_mutex_lock(&mutex);printf("input:");fgets(buff,127,stdin);pthread_mutex_unlock(&mutex);if(strncmp(buff,"end",3) == 0){break;}int n = rand()%3 + 1;sleep(n);}//等待函数线程的结束pthread_join(id,NULL);pthread_mutex_destroy(&mutex);exit(0);
}

在这里插入图片描述

读写锁

读写锁是更高级的互斥锁,有更高的并行性。互斥锁只允许一个线程对临界区访问,而读写锁可以让多个读者并发访问。
读写锁可以多个读者读,但只允许一个写者写。

  1. 如果一个线程用读锁锁定了临界区,那么其他线程也可以用读锁来进入临界区,这样就可以多个线程并行操作。但这个时候,如果再进行写锁加锁就会发生阻塞,写锁请求阻塞后,后面如果继续有读锁来请求,这些后来的读锁都会被阻塞!这样避免了读锁长期占用资源,防止写锁饥饿!
  2. 如果一个线程用写锁锁住了临界区,那么其他线程不管是读锁还是写锁都会发生阻塞!
/* 读写锁的销毁 */
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
/* 读写锁的初始化 */
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,const pthread_rwlockattr_t *restrict attr);
pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;/* 读写锁加锁解锁 */
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);int pthread_rwlock_timedrdlock(pthread_rwlock_t *restrict rwlock, const struct timespec *restrict abs_timeout);
int pthread_rwlock_timedwrlock(pthread_rwlock_t *restrict rwlock, const struct timespec *restrict abs_timeout);int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

我们使用读写锁来实现两个线程读写一段数据
主线程写入时加写锁,两个函数线程读取时加读锁,这样两个函数线程可以同时读取,

# include<unistd.h>
#include <stdlib.h>
# include<stdio.h>
# include<string.h>
# include<time.h>
# include<assert.h>
# include<pthread.h>pthread_rwlock_t rwlock;//创建读写锁
char buff[128]={0};void* fun(void* arg)//读数据
{while(1){pthread_rwlock_rdlock(&rwlock);if(strncmp(buff,"end",3)==0){break;}printf("fun:%s\n",buff);//memset(buff,0,128);int n=rand()%3+1;sleep(n);pthread_rwlock_unlock(&rwlock);n=rand()%3+1;sleep(1);}
}
void* fun1(void* arg)//读数据
{while(1){pthread_rwlock_rdlock(&rwlock);if(strncmp(buff,"end",3)==0){break;}printf("fun1:%s\n",buff);//memset(buff,0,128);int n=rand()%3+1;sleep(n);pthread_rwlock_unlock(&rwlock);n=rand()%3+1;sleep(1);}
}
int main()
{srand((unsigned int)(time(NULL)*time(NULL)));pthread_rwlock_init(&rwlock,NULL);//初始化pthread_t id[2];int res=pthread_create(&id[0],NULL,fun,NULL);//创建线程int r=pthread_create(&id[1],NULL,fun1,NULL);assert(res==0);while(1)//写数据{pthread_rwlock_wrlock(&rwlock);printf("input:");fgets(buff,127,stdin);pthread_rwlock_unlock(&rwlock);if(strncmp(buff,"end",3)==0){break;}int n=rand()%3+1;sleep(1);}pthread_join(id[0],NULL);pthread_join(id[1],NULL);pthread_rwlock_destroy(&rwlock);
}

在这里插入图片描述

条件变量

条件变量是利用线程间共享全局变量进行同步的一种机制。一个线程修改条件,另一个线程等待条件,一旦等到自己需要的条件,就去运行。条件变量用pthread_cond_t类型的实例表示。

信号量

这个信号量和进程间用的信号量作用类似,当线程访问一些有限的公共资源时,就必须做到线程间同步访问。其实就类似于一个计数器,有一个初始值用于记录临界资源的个数。信号量由sem_t的实例表示。

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

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

相关文章

如何给Linux操作系统(CentOS 7为例)云服务器配置环境等一系列东西

1.首先&#xff0c;你得去购买一个云服务器&#xff08;这里以阿里云学生服务器为例&#xff0c;学生必须实名认证&#xff09; 打开阿里云&#xff0c;搜索学生服务器点击进入即可 公网ip为连接云服务器的主机 自定义密码为连接云服务器是需要输入的密码 购买即可 点击云服…

Linux系统编程---I/O多路复用

文章目录1 什么是IO多路复用2 解决什么问题说在前面I/O模型阻塞I/O非阻塞I/OIO多路复用信号驱动IO异步IO3 目前有哪些IO多路复用的方案解决方案总览常见软件的IO多路复用方案4 具体怎么用selectpollepolllevel-triggered and edge-triggered状态变化通知(edge-triggered)模式下…

c#中textbox属性_C#.Net中的TextBox.MaxLength属性与示例

c#中textbox属性Here we are demonstrating use of MaxLength property of TextBox. 在这里&#xff0c;我们演示了TextBox的MaxLength属性的使用。 MaxLength property of TextBox is used to set maximum number of character that we can input into a TextBox. Limit of M…

IIS7 MVC网站生成、发布

(1)生成。 确保System.Web.Mvc.dll在bin目录下 (2)发布网站到文件系统 (3)在IIS中为网站添加应用程序池&#xff08;一个虚拟目录&#xff0c;一个应用程序池&#xff09; (4)添加在默认网站下添加虚拟目录 &#xff08;5&#xff09;转换为应用程序 至此&#xff0c;部署完毕 …

C语言多维数组

文章目录多维数组数组名下标指向数组的指针作为函数参数的多维数组指针数组小结多维数组 如果某个数组的维数超过1&#xff0c;它就被称为多维数组&#xff0c;例如&#xff0c;下面这个声明&#xff1a; int matrix[6][10]创建了一个包含60个元素的矩阵。但是&#xff0c;它…

fwrite函数的用法示例_C语言中的fwrite()函数(带有示例)

fwrite函数的用法示例C中的fwrite()函数 (fwrite() function in C) Prototype: 原型&#xff1a; size_t fwrite(void *buffer, size_t length, size_t count, FILE *filename);Parameters: 参数&#xff1a; void *buffer, size_t length, size_t count, FILE *filenameRetu…

伙伴算法、slab机制、内存管理函数

文章目录1 伙伴算法页框操作alloc_pages()2 slabslab机制要解决的问题使用高速缓存3 内存管理函数kmallockzallocvmallocvzalloc区别参考文章内核使用struct page结构体描述每个物理页&#xff0c;也叫页框。内核在很多情况下&#xff0c;需要申请连续的页框&#xff0c;而且数…

Javaweb---监听器

1.什么是监听器 监听器就是监听某个对象的状态变化的组件。 事件源&#xff1a;被监听的对象 ----- 三个域对象 request session servletContext 监听器&#xff1a;监听事件源对象 事件源对象的状态的变化都会触发监听器 ---- 62 注册监听器&#xff1a;将监听器与事件源进行…

Linux中的Ramdisk和Initrd

Ramdisk简介先简单介绍一下ramdisk&#xff0c;Ramdisk是虚拟于RAM中的盘(Disk)。对于用户来说&#xff0c;能把RAM disk和通常的硬盘分区&#xff08;如/dev/hda1&#xff09;同等对待来使用&#xff0c;例如&#xff1a;redice # mkfs.ext2 /dev/ram0mke2fs 1.38 (30-Jun-200…

slab下kmalloc内核函数实现

文章目录kmalloc的整体实现获取高速缓存高速缓存获取index总结https://blog.csdn.net/qq_41683305/article/details/124554490&#xff0c;在这篇文章中&#xff0c;我们介绍了伙伴算法、slab机制和常见的内存管理函数&#xff0c;接下来&#xff0c;我们看看kmalloc内核函数的…

标题:三羊献瑞

标题&#xff1a;观察下面的加法算式&#xff1a; 其中&#xff0c;相同的汉字代表相同的数字&#xff0c;不同的汉字代表不同的数字。 请你填写“三羊献瑞”所代表的4位数字&#xff08;答案唯一&#xff09;&#xff0c;不要填写任何多余内容。 思路分析&#xff1a; 首先…

进程虚拟地址管理

文章目录1 地址分布实际使用中的内存区域2 进程的虚拟地址描述用户空间mmap线程之间共享内存地址的实现机制1 地址分布 现在采用虚拟内存的操作系统通常都使用平坦地址空间&#xff0c;平坦地址空间是指地址空间范围是一个独立的连续空间&#xff08;比如&#xff0c;地址从0扩…

标题:加法变乘法

标题&#xff1a;我们都知道&#xff1a;123 … 49 1225 现在要求你把其中两个不相邻的加号变成乘号&#xff0c;使得结果为2015 比如&#xff1a; 123…10*1112…27*2829…49 2015 就是符合要求的答案。 请你寻找另外一个可能的答案&#xff0c;并把位置靠前的那个乘号左…

【翻译】eXpressAppFramework QuickStart 业务模型设计(四)—— 实现自定义业务类...

这一讲&#xff0c;你将学到如何从头开始实现业务类。为此&#xff0c;将要实现Department和Position业务类。这些类将被应用到之前实现的Contact类中。你将学到引用对象自动生成用户界面的基本要素。 在此之前&#xff0c;我建议你去阅读一下 【翻译】eXpressAppFramework Qui…

内存重映射

文章目录1 kmap2 映射内核内存到用户空间使用remap_pfn_range使用io_remap_pfn_rangemmap文件操作建立VMA和实际物理地址的映射mmap 之前分配 一次性映射mmap 之前分配 Page FaultPage Fault 中分配 映射内核内存有时需要重新映射&#xff0c;无论是从内核到用户空间还是从内…

math.sqrt 有问题_JavaScript中带有示例的Math.sqrt()方法

math.sqrt 有问题JavaScript | Math.sqrt()方法 (JavaScript | Math.sqrt() Method) The Math.sqrt() method is inbuilt in JavaScript to find the square root of a number. In this tutorial, we will learn about the sqrt() method with examples. JavaScript中内置了Mat…

ISAPI Rewrite 实现简单url重写、二级域名重写

实现步骤&#xff1a; 第一步&#xff1a;下载ISAPI_Rewrite.rar&#xff0c;将Rewrite文件夹和httpd.ini直接放在项目根目录下面。 第二步&#xff1a;IIS配置&#xff0c;筛选Rewrite文件夹里面的Rewrite.dll文件&#xff0c;如图&#xff1a; 第三步&#xff1a;在httpd.ini…

用户登录

用户登录 代码namespace 用户登录 {public partial class Form1 : Form{public Form1(){InitializeComponent();}bool b1, b2, b3, b4, b5, b6;private void button1_Click(object sender, EventArgs e){try{if (b1 && b2 && b3 && b4 && b5 &…

进程上下文和中断上下文

文章目录进程的preempt_count变量thread_infopreempt_counthardirq相关softirq相关上下文原文链接&#xff1a; https://zhuanlan.zhihu.com/p/88883239进程的preempt_count变量 thread_info 在内核中&#xff0c;上下文的设置和判断接口可以参考 include/linux/preempt.h 文…

标题:凑算式

标题&#xff1a;凑算式 这个算式中AI代表19的数字&#xff0c;不同的字母代表不同的数字。 比如&#xff1a; 68/3952/714 就是一种解法&#xff0c; 53/1972/486 是另一种解法。 这个算式一共有多少种解法&#xff1f; 注意&#xff1a;你提交应该是个整数&#xff0c;不要…