Menu

  • Home
  • Work
    • Cloud
      • Virtualization
      • IaaS
      • PaaS
    • Java
    • Go
    • C
    • C++
    • JavaScript
    • PHP
    • Python
    • Architecture
    • Others
      • Assembly
      • Ruby
      • Perl
      • Lua
      • Rust
      • XML
      • Network
      • IoT
      • GIS
      • Algorithm
      • AI
      • Math
      • RE
      • Graphic
    • OS
      • Linux
      • Windows
      • Mac OS X
    • BigData
    • Database
      • MySQL
      • Oracle
    • Mobile
      • Android
      • IOS
    • Web
      • HTML
      • CSS
  • Life
    • Cooking
    • Travel
    • Gardening
  • Gallery
  • Video
  • Music
  • Essay
  • Home
  • Work
    • Cloud
      • Virtualization
      • IaaS
      • PaaS
    • Java
    • Go
    • C
    • C++
    • JavaScript
    • PHP
    • Python
    • Architecture
    • Others
      • Assembly
      • Ruby
      • Perl
      • Lua
      • Rust
      • XML
      • Network
      • IoT
      • GIS
      • Algorithm
      • AI
      • Math
      • RE
      • Graphic
    • OS
      • Linux
      • Windows
      • Mac OS X
    • BigData
    • Database
      • MySQL
      • Oracle
    • Mobile
      • Android
      • IOS
    • Web
      • HTML
      • CSS
  • Life
    • Cooking
    • Travel
    • Gardening
  • Gallery
  • Video
  • Music
  • Essay

POSIX线程编程

2
Jul
2009

POSIX线程编程

By Alex
/ in C,Linux
/ tags 并发编程
0 Comments
基本知识
什么是线程

一个程序中可以有多个代码指向序列,这每个序列就是一个线程(Thread),线程是进程内部的一个控制序列。一个进程至少具有一个执行线程。

Linux中通过fork创建的新进程,与Phtead API创建的线程是具有很大不同的:

  1. fork出的进程,拥有自己的变量、PID,其时间调度是独立的,其执行几乎完全独立于父进程
  2. 进程中创建的新线程,具有自己的栈(因而具有独立的局部变量),但是它与创建者共享全局变量、文件描述符、信号处理函数、当前目录状态

尽管线程已经出现很长时间,但是在IEEE POSIX委员会发布相关标准前,并没有在UNIX系统中得到广泛的支持。POSIX 1003.1c规范改变了这一状况,线程的实现被标准化,绝大部分的Linux发行版都支持它。

Linux在1996年开始支持线程,当时的函数库称为LinuxThread,该线程实现和POSIX标准存在细微的差别,特别是信号处理的相关部分。许多项目致力于改善Linux对线程的支持,以清除与POSIX标准的差异、增加性能,其中大部分工作集中在如何将用户级线程映射到内核级线程。这些项目中,以下一代POSIX线程(NGPT)和本地POSIX线程库(NPTL)为代表,而NPTL成为了Linux线程库的新标准。

线程的优缺点

尽管相比起某些其它OS,Linux创建进程的效率很高,但是创建新线程比创建新进程的代价要小得多。

线程具有以下优势场景:

  1. 有时,一个程序需要“同时”做几件事情。例如:编辑文档的时候同时进行单词个数的统计;数据库软件需要同时服务多个连接,这些连接可以对应不同的线程,线程之间需要紧密协作才能完成加锁、数据一致性要求,这些需求通过线程很容易实现
  2. 混杂着输入、计算、输出的应用程序。分离为多个线程来处理,可以有效改善程序的性能。需要服务多个客户端的网络服务器也是天生适合多线程的例子
  3. 一般而言,线程之间的切换需要操作系统做的工作比进程切换少得多。多个线程对资源的需求要远远小于多个进程

线程的主要缺点是:

  1. 多线程程序需要非常仔细的设计。在多线程程序中,因为时序上的细微差别、无意间的变量共享引发错误的可能性很大
  2. 多线程程序的调试比单线程困难的多,因为线程之间的交互非常难于控制
  3. 对于计算密集型程序,将计算拆分到多个线程运行,对于单处理器来说没有价值
pthread编程基础

Linux下线程具有一套完整的库函数,这些函数大部分以pthread_开头,声明在头文件: pthread.h 中,链接时需要指定-lpthread。

在最初设计UNIX/POSIX例程时,人们往往假设每个进程只有一个执行线程,典型的例子就是errno全局变量。在多线程环境下,需要“可重入例程”:即多次调用仍然能正常工作,这些调用可能来自不同的线程,也可能是某种形式的递归调用。

编写多线程程序时,通常需要在任何include语句之前定义宏 _REENTRANT ,以启用“可重入”功能,它将做以下三件事情:

  1. 对部分函数进行重新定义,定义为其可重入版本,这些函数的名字一般不会发生改变,只是在原函数的名字后面加上 _r 
  2. stdio.h中原来以宏实现的一些函数,变成可重入函数
  3. errno.h中定义的变量errno变成一个函数调用,能够以多线程的形式来获取真正的errno值

下面是基本的线程API:

C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include <pthread.h>
/**
* 创建一个新的线程
* @param thread 指向pthread_t的指针,线程被创建时,指针指向的变量被写入一个标识符,该标识符用来引用新的线程
* @param attr 设置线程的属性,不需要时设置为NULL
* @param start_routine 线程启动时执行的函数
* @param arg 线程启动时指向的函数的参数
*
* @return 成功时返回0,错误时返回错误代码,pthread_函数的大部分都没有遵循UNIX函数惯例——失败时返回-1
*/
int pthread_create( pthread_t *thread, pthread_attr_t *attr,
        void *(*start_routine)( void * ), void *arg );
 
/**
* 终止一个线程的执行(谁调用,谁被终止),类似于exit()终止一个进程的执行
* @param retval 存放返回值的指针,注意不能使用局部变量,因为线程退出后已经销毁
*/
void pthread_exit( void *retval );
 
/**
* 类似于进程中用来收集子进程信息的wait函数
* @param th 将要等待的线程
* @param thread_return 指向一个指针,该指针指向线程的返回值
*/
int pthread_join( pthread_t th, void **thread_return );
 
/**
* 可以让一个线程要求另外一个线程终止,就好像进程之间发送信号一样。
* @param thread 需要终止的线程的标识符
*/
int pthread_cancel( pthread_t thread );
/**
* 可以设置自己的取消状态
* @param state  PTHREAD_CANCEL_ENABLE 允许取消请求(默认);PTHREAD_CANCEL_DISABLE 忽视取消请求
*/
int pthread_setcancelstate( int state, int *oldstate );
/**
* 可以设置自己的取消方式
* @param type
*    PTHREAD_CANCEL_ASYNCHRONOUS 接收到请求后立即采取行动;
*    PTHREAD_CANCEL_DEFERRED 延迟取消行为(默认),直到线程执行了以下函数之一:
*      pthread_join, pthread_cond_wait,pthread_cond_timedwait, pthread_testcancel, sem_wait, sigwait
*/
int pthread_setcanceltype( int type, int *oldtype );

下面是一个例子:

C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
void *thread_function( void *arg );
//一个共享变量
char message[] = "Hello";
int main()
{
    setbuf( stdout, _IOFBF );
    int res;
    pthread_t a_thread;
    void *thread_result; //存放线程返回值的指针
    //创建一个线程
    res = pthread_create( &a_thread, NULL, thread_function, ( void * ) message );
    if ( res != 0 )
    {
        perror( "Thread creation failed" );
        exit( EXIT_FAILURE );
    }
    printf( "Waiting for thread to finish...\n" );
    //等待线程运行结束,将返回值指针的地址传递给函数,线程指向完毕后,会填写此地址
    res = pthread_join( a_thread, &thread_result );
    if ( res != 0 )
    {
        perror( "Thread join failed" );
        exit( EXIT_FAILURE );
    }
    //打印线程的返回值
    printf( "Thread joined, it returned message: %s\n", ( char * ) thread_result );
    //打印共享变量
    printf( "Message changed: %s\n", message );
    exit( EXIT_SUCCESS );
}
//线程入口函数定义
void *thread_function( void *arg )
{
    printf( "Thread is running. Argument: %s\n", ( char * ) arg );
    sleep( 3 );
    //修改共享变量
    strcpy( message, "Bye" );
    //线程退出,返回一段文本
    pthread_exit( "Thread exit" );
}
线程属性

在创建线程的时候,允许指定一个属性参数,下面的API用于控制属性:

C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <pthread.h>
/**
* 初始化一个线程属性
*/
int pthread_attr_init( pthread_attr_t *attr );
/**
* 销毁一个线程属性,一旦被销毁,除非再次初始化,否则不能继续使用
*/
int pthread_attr_destroy( pthread_attr_t * );
 
//线程属性控制函数
//detachstate:允许无需对线程进行rejoin
//detachstate可以为:PTHREAD_CREATE_JOINABLE(默认)、PTHREAD_CREATE_DETACHED,后者不允许针对其pthread_join调用
int pthread_attr_setdetachstate( pthread_attr_t *attr, int detachstate );
int pthread_attr_getdetachstate( const pthread_attr_t *attr, int *detachstate );
//schedpolicy:控制线程如何被调度。可选值:SCHED_OTHER(默认)、SCHED_RP、SCHED_FIFO
//后两个值要求超级用户权限,分别使用循环调度、先进先出调度策略
int pthread_attr_setschedpolicy( pthread_attr_t *attr, int policy );
int pthread_attr_getschedpolicy( const pthread_attr_t *attr, int *policy );
//scheparam:与schedpolicy配合使用,可以控制SCHED_OTHER的行为
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 );
//inheritsched:是否从创建者继承调度属性。可选值:PTHREAD_EXPLICIT_SCHED、PTHREAD_INHERIT_SCHED
//分别表示需要明确设定,以及从创建者继承
int pthread_attr_setinheritsched( pthread_attr_t *attr, int inherit );
int pthread_attr_getinheritsched( const pthread_attr_t *attr, int *inherit );
//scope:控制线程调度的计算方式,Linux目前仅仅支持PTHREAD_SCOPE_SYSTEM
int pthread_attr_setscope( pthread_attr_t *attr, int scope );
int pthread_attr_getscope( const pthread_attr_t *attr, int *scope );
//stacksize:控制线程栈大小,单位字节
int pthread_attr_setstacksize( pthread_attr_t *attr, int scope );
int pthread_attr_getstacksize( const pthread_attr_t *attr, int *scope );

下面是一个控制线程优先级的例子:

C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/sched.h>
 
int main( int argc, char **argv )
{
    pthread_attr_t thread_attr;
    int max_priority;
    int min_priority;
    struct sched_param scheduling_value;
 
    int res = pthread_attr_setschedpolicy( &thread_attr, SCHED_OTHER );
    if ( res != 0 )
    {
        perror( "Setting scheduling policy failed" );
        exit( EXIT_FAILURE );
    }
    max_priority = sched_get_priority_max( SCHED_OTHER );
    min_priority = sched_get_priority_min( SCHED_OTHER );
    scheduling_value.sched_priority = min_priority; //设置为最低优先级
    res = pthread_attr_setschedparam( &thread_attr, &scheduling_value );
    if ( res != 0 )
    {
        perror( "Setting scheduling priority failed" );
        exit( EXIT_FAILURE );
    }
}
同步

Linux包含一组控制线程执行和代码临界区域访问的方法。 

信号量

信号量这个概念最初由Dijkstra提出,他是一种特殊的变量,可以被增加或减少,即使在多线程环境下,对其进行增加、减少等关键操作也能保证原子性。这意味着多个线程对信号量的操作将被顺序执行,而对于普通变量,来自程序中多个线程的冲突操作造成的结果是不能确定的。

信号量用于控制对一组相同对象的访问。Linux下有两组函数可以用于信号量:

  1. 一组取自POSIX的扩展,用于线程
  2. 一组被称为System V信号量,用于进程的同步

Linux可用的信号量包括两种:二进制信号量、计数信号量,前者只有0/1两个值,可以用来保护代码同时只能被一个线程访问。线程中使用的基本信号量函数有以下四个:

C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <semaphore.h>
/**
* 创建一个信号量
* @param sem 被初始化的信号量
* @param pshared 信号量的类型,如果为0表示是进程内部信号量,如果为1表示可以在多个进程之间共享
* @param value 信号量初始值
*/
int sem_init( sem_t *sem, int pshared, unsigned int value );
/**
* 以原子的方式将信号量的值加1
* 所谓原子操作是指:如果两个线程同时企图调用该函数,他们不会互相干扰,结果总是一致的加2
*/
int sem_post( sem_t * sem );
/**
* 以原子的方式将信号量的值减1,但它会等到直到信号量有个非零值时才会指向减法操作
* 如果对值为0的信号量调用该操作,调用者将一直等待直到信号量不为0为止
*/
int sem_wait( sem_t * sem );
//尝试获取一个信号量,立即返回
int sem_trywait( sem_t *sem );
//等待一个信号量,在指定时间后超时退出
int sem_timedwait( sem_t *sem, const struct timespec *abstime );
/**
* 在用完信号量后对其进行清理
*/
int sem_destroy( sem_t * sem );
互斥量

用于控制同一时刻只能有一个线程可以访问共享内存,互斥量允许锁住某个对象,直到操作完成后再释放它。

C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <pthread.h>
//这些函数成功返回0,失败返回错误代码,不设置errno
/**
* 初始化一个互斥量
* @param mutex 互斥量
* @param mutexattr 互斥量的属性,可以定制其行为
*/
int pthread_mutex_init( pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr );
/**
* 锁定一个互斥量,如果互斥量已经被加锁,该调用会一直阻塞直到其解锁
*/
int pthread_mutex_lock( pthread_mutex_t *mutex );
/**
* 解锁一个互斥量
*/
int pthread_mutex_unlock( pthread_mutex_t *mutex );
/**
* 销毁一个互斥量
*/
int pthread_mutex_destroy( pthread_mutex_t *mutex );

 

← Linux编程知识集锦
Perl知识集锦 →

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">

Related Posts

  • Linux信号、进程和会话
  • Go语言并发编程
  • Java5新特性
  • Python并发编程
  • Java线程与并发编程

Recent Posts

  • Investigating and Solving the Issue of Failed Certificate Request with ZeroSSL and Cert-Manager
  • A Comprehensive Study of Kotlin for Java Developers
  • 背诵营笔记
  • 利用LangChain和语言模型交互
  • 享学营笔记
ABOUT ME

汪震 | Alex Wong

江苏淮安人,现居北京。目前供职于腾讯云,专注容器方向。

GitHub:gmemcc

Git:git.gmem.cc

Email:gmemjunk@gmem.cc@me.com

ABOUT GMEM

绿色记忆是我的个人网站,域名gmem.cc中G是Green的简写,MEM是Memory的简写,CC则是我的小天使彩彩名字的简写。

我在这里记录自己的工作与生活,同时和大家分享一些编程方面的知识。

GMEM HISTORY
v2.00:微风
v1.03:单车旅行
v1.02:夏日版
v1.01:未完成
v0.10:彩虹天堂
v0.01:阳光海岸
MIRROR INFO
Meta
  • Log in
  • Entries RSS
  • Comments RSS
  • WordPress.org
Recent Posts
  • Investigating and Solving the Issue of Failed Certificate Request with ZeroSSL and Cert-Manager
    In this blog post, I will walk ...
  • A Comprehensive Study of Kotlin for Java Developers
    Introduction Purpose of the Study Understanding the Mo ...
  • 背诵营笔记
    Day 1 Find Your Greatness 原文 Greatness. It’s just ...
  • 利用LangChain和语言模型交互
    LangChain是什么 从名字上可以看出来,LangChain可以用来构建自然语言处理能力的链条。它是一个库 ...
  • 享学营笔记
    Unit 1 At home Lesson 1 In the ...
  • K8S集群跨云迁移
    要将K8S集群从一个云服务商迁移到另外一个,需要解决以下问题: 各种K8S资源的迁移 工作负载所挂载的数 ...
  • Terraform快速参考
    简介 Terraform用于实现基础设施即代码(infrastructure as code)—— 通过代码( ...
  • 草缸2021
    经过四个多月的努力,我的小小荷兰景到达极致了状态。

  • 编写Kubernetes风格的APIServer
    背景 前段时间接到一个需求做一个工具,工具将在K8S中运行。需求很适合用控制器模式实现,很自然的就基于kube ...
  • 记录一次KeyDB缓慢的定位过程
    环境说明 运行环境 这个问题出现在一套搭建在虚拟机上的Kubernetes 1.18集群上。集群有三个节点: ...
  • eBPF学习笔记
    简介 BPF,即Berkeley Packet Filter,是一个古老的网络封包过滤机制。它允许从用户空间注 ...
  • IPVS模式下ClusterIP泄露宿主机端口的问题
    问题 在一个启用了IPVS模式kube-proxy的K8S集群中,运行着一个Docker Registry服务 ...
  • 念爷爷
      今天是爷爷的头七,十二月七日、阴历十月廿三中午,老人家与世长辞。   九月初,回家看望刚动完手术的爸爸,发

  • 6 杨梅坑

  • liuhuashan
    深圳人才公园的网红景点 —— 流花山

  • 1 2020年10月拈花湾

  • 内核缺陷触发的NodePort服务63秒延迟问题
    现象 我们有一个新创建的TKE 1.3.0集群,使用基于Galaxy + Flannel(VXLAN模式)的容 ...
  • Galaxy学习笔记
    简介 Galaxy是TKEStack的一个网络组件,支持为TKE集群提供Overlay/Underlay容器网 ...
TOPLINKS
  • Zitahli's blue 91 people like this
  • 梦中的婚礼 64 people like this
  • 汪静好 61 people like this
  • 那年我一岁 36 people like this
  • 为了爱 28 people like this
  • 小绿彩 26 people like this
  • 杨梅坑 6 people like this
  • 亚龙湾之旅 1 people like this
  • 汪昌博 people like this
  • 彩虹姐姐的笑脸 24 people like this
  • 2013年11月香山 10 people like this
  • 2013年7月秦皇岛 6 people like this
  • 2013年6月蓟县盘山 5 people like this
  • 2013年2月梅花山 2 people like this
  • 2013年淮阴自贡迎春灯会 3 people like this
  • 2012年镇江金山游 1 people like this
  • 2012年徽杭古道 9 people like this
  • 2011年清明节后扬州行 1 people like this
  • 2008年十一云龙公园 5 people like this
  • 2008年之秋忆 7 people like this
  • 老照片 13 people like this
  • 火一样的六月 16 people like this
  • 发黄的相片 3 people like this
  • Cesium学习笔记 90 people like this
  • IntelliJ IDEA知识集锦 59 people like this
  • 基于Kurento搭建WebRTC服务器 38 people like this
  • Bazel学习笔记 37 people like this
  • PhoneGap学习笔记 32 people like this
  • NaCl学习笔记 32 people like this
  • 使用Oracle Java Mission Control监控JVM运行状态 29 people like this
  • Ceph学习笔记 27 people like this
  • 基于Calico的CNI 27 people like this
  • Three.js学习笔记 24 people like this
Tag Cloud
ActiveMQ AspectJ CDT Ceph Chrome CNI Command Cordova Coroutine CXF Cygwin DNS Docker eBPF Eclipse ExtJS F7 FAQ Groovy Hibernate HTTP IntelliJ IO编程 IPVS JacksonJSON JMS JSON JVM K8S kernel LB libvirt Linux知识 Linux编程 LOG Maven MinGW Mock Monitoring Multimedia MVC MySQL netfs Netty Nginx NIO Node.js NoSQL Oracle PDT PHP Redis RPC Scheduler ServiceMesh SNMP Spring SSL svn Tomcat TSDB Ubuntu WebGL WebRTC WebService WebSocket wxWidgets XDebug XML XPath XRM ZooKeeper 亚龙湾 单元测试 学习笔记 实时处理 并发编程 彩姐 性能剖析 性能调优 文本处理 新特性 架构模式 系统编程 网络编程 视频监控 设计模式 远程调试 配置文件 齐塔莉
Recent Comments
  • qg on Istio中的透明代理问题
  • heao on 基于本地gRPC的Go插件系统
  • 黄豆豆 on Ginkgo学习笔记
  • cloud on OpenStack学习笔记
  • 5dragoncon on Cilium学习笔记
  • Archeb on 重温iptables
  • C/C++编程:WebSocketpp(Linux + Clion + boostAsio) – 源码巴士 on 基于C/C++的WebSocket库
  • jerbin on eBPF学习笔记
  • point on Istio中的透明代理问题
  • G on Istio中的透明代理问题
  • 绿色记忆:Go语言单元测试和仿冒 on Ginkgo学习笔记
  • point on Istio中的透明代理问题
  • 【Maven】maven插件开发实战 – IT汇 on Maven插件开发
  • chenlx on eBPF学习笔记
  • Alex on eBPF学习笔记
  • CFC4N on eBPF学习笔记
  • 李运田 on 念爷爷
  • yongman on 记录一次KeyDB缓慢的定位过程
  • Alex on Istio中的透明代理问题
  • will on Istio中的透明代理问题
  • will on Istio中的透明代理问题
  • haolipeng on 基于本地gRPC的Go插件系统
  • 吴杰 on 基于C/C++的WebSocket库
©2005-2025 Gmem.cc | Powered by WordPress | 京ICP备18007345号-2