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,一经查实,立即删除!

相关文章

博客上一些项目相关源码链接

GitHub&#xff1a;https://github.com/beyondyanyu/Sayingyy

重新开启Ctrl+Alt+Backspace快捷键

UBUNTU老用户知道CtrlAltBackspace这个快捷键是用来快速重启X的在9.04中被默认关闭了&#xff0c;那如何来打开它呢&#xff1f;在终端中输入&#xff1a;sudo gedit /etc/X11/xorg.conf在其中加入&#xff1a;Section “ServerFlags”Option “DontZap” “false”EndSection退…

Java LocalDate类| 带示例的getDayOfYear()方法

LocalDate类的getDayOfYear()方法 (LocalDate Class getDayOfYear() method) getDayOfYear() method is available in java.time package. getDayOfYear()方法在java.time包中可用。 getDayOfYear() method is used to get the day-of-year field value of this LocalDate obje…

火腿三明治定理

定理&#xff1a;任意给定一个火腿三明治&#xff0c;总有一刀能把它切开&#xff0c;使得火腿、奶酪和面包片恰好都被分成两等份。 而且更有趣的是&#xff0c;这个定理的名字真的就叫做“火腿三明治定理”&#xff08;ham sandwich theorem&#xff09;。它是由数学家亚瑟•斯…

如何给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)模式下…

[转帖]纯属娱乐——变形金刚vs天网

[转帖]变形金刚2的影评-《变形金刚3 天网反击战》有一个问题困扰了我足足二十年&#xff1a;为什么汽车人要帮地球人&#xff1f;光用“所有有感知的生物都应享有自由”这个法则是根本说不过去的&#xff0c;因为猪也有感知&#xff0c;但人类就把猪圈养起来&#xff0c;随意杀…

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;部署完毕 …

标题:明码

转载&#xff1a;https://blog.csdn.net/u011747888/article/details/79781040 标题&#xff1a;明码 汉字的字形存在于字库中&#xff0c;即便在今天&#xff0c;16点阵的字库也仍然使用广泛。 16点阵的字库把每个汉字看成是16x16个像素信息。并把这些信息记录在字节中。 一…

C语言多维数组

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

ubuntu路由器联网_路由器及其协议简介| 联网

ubuntu路由器联网路由器简介 (Introduction to Router) Routers are network layer devices. Data on the network layer is known as packets. Routers work to forward packets from one network to another. Routers also maintain the address table. 路由器是网络层设备。…

XPath学习:轴(5)——descendant-or-self

XPath 是一门在 XML 文档中查找信息的语言。XPath 可用来在 XML 文档中对元素和属性进行遍历。 XPath 是 W3C XSLT 标准的主要元素&#xff0c;并且 XQuery 和 XPointer 同时被构建于 XPath 表达之上。 推荐一个挺不错的网站&#xff1a;http://www.zvon.org/xxl/XPathTutorial…

linux设备驱动开发---平台设备驱动程序

文章目录1 平台驱动程序2 平台设备2.1 资源和平台数据1 设备配置---废弃的旧方法资源平台数据声明平台设备2 设备配置---推荐的新方法3 设备、驱动程序和总线匹配OF风格ACPIID表匹配匹配平台设备的名字和平台驱动的名字平台设备和平台驱动程序如何匹配4 Platfrom架构驱动程序有…

标题:乘积尾零

标题&#xff1a;乘积尾零 如下的10行数据&#xff0c;每行有10个整数&#xff0c;请你求出它们的乘积的末尾有多少个零&#xff1f; 5650 4542 3554 473 946 4114 3871 9073 90 4329 2758 7949 6113 5659 5245 7432 3051 4434 6704 3594 9937 1173 6866 3397 4759 7557 3070…

Robots.txt指南

Robots.txt指南当搜索引擎访问一个网站时&#xff0c;它首先会检查该网站的根域下是否有一个叫做robots.txt的纯文本文件。Robots.txt文件用于限定搜索引擎对其 网站的访问范围&#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…

标题:递增三元组

标题&#xff1a;递增三元组 给定三个整数数组 A [A1, A2, … AN], B [B1, B2, … BN], C [C1, C2, … CN]&#xff0c; 请你统计有多少个三元组(i, j, k) 满足&#xff1a; 1 < i, j, k < NAi < Bj < Ck 【输入格式】 第一行包含一个整数N。 第二行包含N个整…

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

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

eval 函数 代替函数_eval()函数以及JavaScript中的示例

eval 函数 代替函数eval()函数 (eval() function) eval() function is a predefined global function in JavaScript and it is used to evaluate (execute) an expression, which is passed to the function as a parameter. It can also evaluate any JavaScript code. eval(…