【Linux应用编程】POSIX线程基本操作

文章目录

  • 1 什么是POSIX线程
  • 2 POSIX线程优缺点
    • 2.1 进程与线程
    • 2.2 为什么要使用线程
    • 2.3 Pthreads优势
    • 2.3 Pthreads不足
  • 3 POSIX线程常用API
    • 3.1 线程创建
    • 3.2 线程脱离
    • 3.3 等待线程结束
    • 3.4 线程退出
    • 3.5 线程号获取
    • 3.6 线程号比较
    • 3.7 线程例子
  • 4 参考文章


1 什么是POSIX线程

  可移植操作系统接口(Portable Operating System Interface of UNIX),简称POSIX,POSIX标准定义了操作系统应该为应用程序提供的接口标准,由电气和电子工程师协会 ( Institute of Electrical and Electronics Engineers,简称IEEE)定义在各种UNIX操作系统上运行软件使用的一系列API标准的总称。随着操作系统的发展,POSIX标准已经成为信息界一个标准化规范,以提高不同操作系统的兼容性和应用程序的可移植性。不仅仅是适用于UNIX,主流操作系统Linux、Windows、Mac OS已加入POSIX支持,甚至国产实时系统(RTOS)RT-Thread都支持POSIX规范标准。

  POSIX线程(POSIX threads),简称Pthreads,是POSIX标准中对线程定义的一部分标准规范。Pthreads定义了创建和操作线程的一整套API(包括类型、函数、常量),在编写程序时,只要遵循Pthreads规范,那么这段程序就是可以在任何支持Pthreads的操作系统上运行,实现优良的跨平台特性。

  总的来说,POSIX就是一个标准化规范接口,更详细的POSIX介绍,可以参考文章末尾官方文档。


2 POSIX线程优缺点

2.1 进程与线程

  进程是操作系统资源分配的基本单位,线程是任务调度和执行的基本单位。不同进程拥有独立的代码空间和内存资源,同一进程下的线程是共享当前进程的所有系统资源。进程和线程存在关联,但又有一定的区别,归纳起来有几个本质的不同点。

  • 进程拥有自己独立的资源空间,线程是“轻量进程”,共享进程的空间(内存、堆栈、文件描述符)
  • 进程是线程的“容器”,每个进程至少存在一个线程
  • 进程的创建、调度和销毁过程的资源开销要比线程大
  • 进程的直接访问对象是操作系统,线程的直接访问对象是进程

2.2 为什么要使用线程

  从进程与线程的特性差异对比,线程最大的优势是资源开销比进程要小。线程相比进程,还存在几个优点。

  • 与fork相比, 创建线程无需申请文件描述符、系统资源等,效率高
  • 线程共享进程的资源空间,避免进程复杂IPC机制
  • 线程间的上下文切换要比进程间的上下文切换快得多

注:
线程也存在不足,由于线程是共享进程资源空间,如果一个线程因为异常崩溃导致共享资源(如内存空间)异常,其他线程很可能也会崩溃。进程拥有独立资源空间,以及操作系统的保护机制,一个进程崩溃不会影响其他进程。


2.3 Pthreads优势

  Pthreads规范化标准接口,提高应用程序在不同操作系统执行时的兼容性可移植性,具备优良的跨平台特性。对于程序员来说,可以释放大量的重复编码工作量,提高编码效率。,


2.3 Pthreads不足

  Pthreads是优秀的,在通用操作系统上使用,几乎找不到缺点。但是在资源紧张的嵌入式设备上使用时,由于增加代码空间和内存空间的占用,需要增加硬件资源(RAM和ROM),成本上升。如在RT-Thread上支持Pthreads需要占用更多的资源空间,相应的需要更多资源的RAM和ROM支持。


3 POSIX线程常用API

  Pthreads定义了一套 基于C 语言的类型、函数和常量,API接口声明位于 pthread.h 头文件中,Pthreads所有 API 以 “pthread_”为 前缀,命名根据具体功能翻译,比较直观通俗,可读性优良。Pthreads API主要包括线程管理和一系列线程间通信(同步)机制。

  • 线程管理(Thread management)

  提供了线程创建(creating)、分离(detaching)、调度(joining)以及线程属性操作的一系列函数。


  • 线程间通信(同步)机制

  提供了通信机制的创建、销毁、访问及它们属性操作的一系列函数。

【1】互斥锁(Mutex)

【2】读写锁(read/write lock)

【4】自旋锁(spin lock)

【5】屏障(barrier)

【6】条件变量(Condition variable)

注:
信号量(semaphore)和消息队列(Message queue)不属于Pthreads标准的范畴内,但属于POSIX的标准。因此,API命名风格上也有差异,信号量API以“sem_”为前缀,消息队列以“mq_”为前缀。


  本文主要描述线程管理相关的基本操作,函数命名以 “pthread_”为前缀。

函数组 前缀 例子
线程属性 pthread_attr_ pthread_attr_t
线程操作 pthread_ pthread_create

  大部分 Pthreads 的函数执行成功后返回 0 值,失败则返回一个包含在 errno.h 头文件中的错误代码。


3.1 线程创建

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
  • 功能,创建一个线程
  • thread,线程描述符(ID)
  • attr,线程属性,如果传入 NULL,则使用默认的线程属性
  • start_routine,线程任务入口函数
  • arg,线程私有参数
  • 返回,成功返回0

3.2 线程脱离

int pthread_detach (pthread_t thread);
  • 功能,使调用线程与当前进程脱离,脱离状态(detached)的线程,退出时会自动释放资源
  • thread,线程描述符
  • 返回,成功返回0

注:

一旦线程属性的分离状态设置为 detached,该线程不能被pthread_join函数等待或者重新被设置为joinable状态。


3.3 等待线程结束

int pthread_join (pthread_t thread, void**value_ptr);	
  • 功能,(主线程)等待线程结束(阻塞),并回收线程资源,类似进程的 wait() 函数。如果线程已经结束,函数会立即返回。
  • value_ptr,户定义的指针,用来存储被等待线程的返回值地址
  • 返回,成功返回0

pthread_joinpthread_detach的区别

  两者功能类似,都是在线程结束后用来回收线程占用的资源。

  一个线程默认属性是可结合状态(joinable), 如果一个线程结束运行但主线程没有等待其结束,则它的状态会变成类似于进程中的僵尸进程状态(Zombie Process),部分资源没有被回收(如退出状态码),所以创建线程者(主线程)应该调用pthread_join来等待线程结束,获得线程的退出代码,回收资源(类似进程的wait、waitpid)。

  pthread_join是阻塞函数,直至线程结束,但一些场景下,主线程不能或者不希望被阻塞,此时可以在主线程或者子线程调用pthread_detach函数,使子线程成为脱离状态(detached),这样该线程结束后会自动释放所有资源,而且pthread_detach函数是非阻塞函数,主线程也不会被阻塞。

注:
调用pthread_detach使线程脱离后,再调用pthread_join,会调用失败,立即返回。


3.4 线程退出

void pthread_exit(void *value_ptr);
  • 功能,使线程退出调度
  • value_ptr,户定义的指针,用来存储线程退出状态的返回值地址
  • 返回,无

  线程的最外层函数的结束可以不用调用pthread_exit函数来退出线程,因为线程return时,线程会自动退出调度状态,系统会回收线程资源。一般情况下,我们在线程内部,在满足特定条件时,需要退出线程,才调用pthread_exit使线程终止并退出。

注:
如果多个线程共享一段资源,一个线程退出后,并不会释放共享资源,直至所有线程退出调度,系统才会回收共享资源。


3.5 线程号获取

  我知道,在使用pthread_create函数创建线程时,第一个参数保存的就是返回线程ID,为什么Pthreads还需要提供一个获取线程号的函数呢?那是因为,pthread_create创建线程返回的线程号,前提是主线程必须先执行,否则返回的是一个未知值。因此,线程内获取线程号应该使用pthread_self函数。

pthread_t pthread_self(void);
  • 功能,获取线程自身的序号(在线程内调用)
  • 参数,无
  • 返回,线程号

例子:线程中将自己分离

pthread_detach(pthread_self());

3.6 线程号比较

  在POSIX线程中,线程号(ID)的类型用 pthread_t 描述,在不同的系统下, pthread_t 的类型可能不同,比如在Ubuntn中是 unsigned long 类型,在solaris系统中则是 unsigned int 类型,而在FreeBSD中是 pthread_t。因此,判断线程号是否相等,不能直接使用 “==” 判断,应该使用Pthreads线程比较函数 pthread_equal检查。

int pthread_equal(pthread_t t0, pthread_t t1);
  • 功能,比较两个线程号是否相等
  • t0,线程号1
  • t1,线程号2
  • 返回,线程号相等返回1,否则返回0

3.7 线程例子

#include 
#include 
#include 
#include "pthread.h" 

void *thread_entry(void *data)  
{
	uint32_t count = 0;
	
	printf("thread0 startup\r\n");
	for(;;)
    {
		printf("thread[%lu], run time[%u]\r\n", pthread_self(), count);
		if (count >= 5)
		{
			pthread_exit(NULL);
		}
		count++;
		sleep(1);
    }
	printf("thread0 exit\r\n");
}
 
int main(int argc, char **argv)  
{
	pthread_t thread0; 
    void *retval; 
	
    pthread_create(&thread0, NULL, thread_entry, 0);
    pthread_join(thread0, &retval);
    
	return 0;
 }

运行结果:

acuity@ubuntu:/mnt/hgfs/LSW/STHB/pthreads/pthread$ ./app 
thread0 startup
thread[139872526268160], run time[0]
thread[139872526268160], run time[1]
thread[139872526268160], run time[2]
thread[139872526268160], run time[3]
thread[139872526268160], run time[4]
thread[139872526268160], run time[5]
thread0 exit

注:
编译Pthreads相关应用程序时,需加上Pthreads链接库(“-lpthread”),如编译“app.c”源码。
gcc app.c -o app -lpthread


4 参考文章

【1】POSIX官方文档

【2】POSIX标准介绍

你可能感兴趣的:(Linux应用编程,#,POSIX线程)