结合源码的Linux线程基础详细整理

2021/10/24 7:10:06

本文主要是介绍结合源码的Linux线程基础详细整理,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

结合源码的Linux线程基础详细整理

  • 一、多线程程序的编译和一些准备
  • 二、线程的创建和销毁
  • 三、线程属性
  • 四、简单的线程入门实例

本篇博客主要是对Linux下的多线程基础使用方法的记录,以便在使用时快速查找。后续会陆续整理多线程通信的知识,有什么错误和建议请留言指教。

一、多线程程序的编译和一些准备

  1. 包含头文件pthread.h
#include <pthread.h>
  1. 添加编译选项-lpthread-D_GNU_SOURCE
gcc mainmutex2.c -o mutexrun -std=c99 -lpthread -D_GNU_SOURCE

宏定义-D_GNU_SOURCE能够允许程序访问一些GNU/Linux扩展函数,不添加会导致编译不通过。

  1. 为了分析多线程的切换和运行顺序,多线程程序需要绑定线程运行CPU(绑核)。

在分析多线程的线程运行顺序时,可能会遇到明明觉得代码没有问题,可是线程运行的顺序就是与理论的不一致的情况,这时候有可能是多核处理器的原因,操作系统自动将多个线程分配到多个CPU中同时运行了,所以会导致像线程优先级设置等线程切换方法不起作用,所以在学习阶段就可以使用程序使所有线程都绑定到同一个CPU上运行,以便分析线程切换的逻辑。
一个绑核代码的实例如下:

int attach_cpu(int cpu_index){
    int cpu_num = sysconf(_SC_NPROCESSORS_CONF);    
    if (cpu_index < 0 || cpu_index >= cpu_num){        
		printf("cpu index ERROR!\n");        
		return -1;    
    }
    cpu_set_t mask;    
    CPU_ZERO(&mask);    
    CPU_SET(cpu_index, &mask);
    if (pthread_setaffinity_np(pthread_self(), sizeof(mask), &mask) < 0){        
    	printf("set affinity np ERROR!\n");        
    	return -1;    
    }
    return 0;
}

int main(void){
    attach_cpu(0);   //将主线程绑定到0核运行
}

二、线程的创建和销毁

首先,线程是怎么表示的呢?在Linux中线程就是通过数据类型pthread_t表示的,所有对线程的操作都需要指定一个线程,而pthread_t就可以指定一个唯一的线程。那pthread_t到底是什么呢?在/usr/include/bits/pthreadtypes.h有对pthread_t的定义:

/* Thread identifiers.  The structure of the attribute type is not
   exposed on purpose.  */
typedef unsigned long int pthread_t;

所以,其实pthread_t就是一个 long int 长整型变量,称之为线程id。

  1. 创建线程pthread_create
/* Create a new thread, starting with execution of START-ROUTINE
   getting passed ARG.  Creation attributed come from ATTR.  The new
   handle is stored in *NEWTHREAD.  */
extern int pthread_create (pthread_t *__restrict __newthread,
						   const pthread_attr_t *__restrict __attr,
						   void *(*__start_routine) (void *),
						   void *__restrict __arg);
参数描述
thread_t *__restrict __newthread线程句柄, 指向线程标识符指针
pthread_attr_t *attr线程属性,可以指定线程属性对象,也可以使用默认值NULL
void * (*__start_routine)(void *)线程起始运行的函数地址,一旦线程被创建就会执行
void *__restrict __arg传递给线程运行函数的参数,需要使用void * 类型,多个参数可封装成结构体,如果没有参数传递,则使用 NULL
返回值 int返回错误码,创建成功则返回0
  1. 线程退出pthread_exit
/* Terminate calling thread. */
extern void pthread_exit (void *__retval)

pthread_exit 用于显式地退出一个线程。通常情况下,pthread_exit() 函数是在线程完成工作后无需继续存在时被调用。
如果 main() 调用了 pthread_exit() ,那么在main线程终止时其他线程将继续执行。否则,它们将在 main() 结束时自动被终止。

  1. 线程取消pthread_cancel
/* Cancel THREAD immediately or at the next possibility.  */
extern int pthread_cancel (pthread_t __th);
参数描述
pthread_t __th线程句柄, 指向线程标识符指针

pthread_cancel调用并不等待线程终止,它只提出请求。线程在取消请求(pthread_cancel)发出后会继续运行,直到到达某个取消点(CancellationPoint)。取消点是线程检查是否被取消并按照请求进行动作的一个位置。点此跳转到详细关于线程取消的知识,人家讲的蛮好的。

  1. 线程连接pthread_join和线程分离pthread_detach
/* Make calling thread wait for termination of the thread TH.  The
   exit status of the thread is stored in *THREAD_RETURN, if THREAD_RETURN
   is not NULL.

   This function is a cancellation point and therefore not marked with
   __THROW.  */
extern int pthread_join (pthread_t __th, void **__thread_return);

/* Indicate that the thread TH is never to be joined with PTHREAD_JOIN.
   The resources of TH will therefore be freed immediately when it
   terminates, instead of waiting for another thread to perform PTHREAD_JOIN
   on it.  */
extern int pthread_detach (pthread_t __th) ;
参数描述
pthread_t __th线程句柄, 指向线程标识符指针
void **__thread_return用户定义的指针,用来存储被等待线程的返回值

当创建一个线程时,它的某个属性会定义它是否是可连接的(joinable)或可分离的(detached)。只有创建时定义为可连接的线程才可以被连接。如果线程创建时被定义为可分离的,则它永远也不能被连接。

A线程调用pthread_join(B)来连接B线程,pthread_join会阻碍A线程,直到B线程终止为止(也即线程同步,等待线程结束)。B线程终止后,A线程会回收B线程的资源。
A线程调用pthread_detach(B)来分离B线程,A线程与B线程分离,B线程结束后,资源由系统自动回收。

因此有以下四种回收线程资源避免僵尸线程的方法
方法1:创建线程前,利用pthread_attr_setdetachstate将线程设为detached,这样线程退出时,系统自动回收线程资源。

方法2:创建线程后,用pthread_detach将其设置为detached。

方法3:线程B退出后,线程A调用pthread_join来主动释放线程B的资源。pthread_join可能发生阻塞。

方法4:在线程任务中,pthread_detach(pthread_self())来分离自身线程;

三、线程属性

前面提到,线程是具有自己的属性的,其属性由专门的属性对象pthread_attr_t 来描述。属性对象常用的相关的操作有初始化和销毁。
Linux中关于线程相关数据类型的定义均位于/usr/include/bits/pthreadtypes.h中。

typedef union
{
  char __size[__SIZEOF_PTHREAD_MUTEXATTR_T];
  int __align;
} pthread_mutexattr_t;
/* Initialize thread attribute *ATTR with default attributes
   (detachstate is PTHREAD_JOINABLE, scheduling policy is SCHED_OTHER,
    no user-provided stack).  */
extern int pthread_attr_init (pthread_attr_t *__attr);

/* Destroy thread attribute *ATTR.  */
extern int pthread_attr_destroy (pthread_attr_t *__attr)

/* Get detach state attribute.  */
extern int pthread_attr_getdetachstate (__const pthread_attr_t *__attr,
										int *__detachstate);

/* Set detach state attribute.  */
extern int pthread_attr_setdetachstate (pthread_attr_t *__attr,
										int __detachstate)

Linux下的线程有:绑定属性、分离属性、调度属性、堆栈大小属性和满占警戒区大小属性,对每个属性都有相关的函数进行操作,具体可以点此跳转到详细关于线程属性的讲解。

四、简单的线程入门实例

创建两个线程,都对变量count执行 +1 操作,直到count等于10。

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

int count = 0;

void thread_func1(){
	attach_cpu(0);	// 绑定此线程到0核运行,函数实现见本博客第一节第3小节
    while(count < 10){   
        printf("thread1: %d\n",count);     
        count++; 
        sleep(1);
    }
}
void thread_func2(){
	attach_cpu(0);	// 绑定此线程到0核运行
    while(count < 10){ 
        printf("thread2: %d\n",count);
        count++;        
        sleep(1);    
    }
}

int main(){
	int res = 0
    pthread_t thread1, thread2;
    
    /* 创建线程 */
    res = pthread_create(&thread1, NULL, (void*)thread_func1, NULL);    
    if(0 != res){        
        printf("create thread 1 error ! errNo : %d\n", res);        
        exit(1);    
    }
    res = pthread_create(&thread2, NULL, (void*)thread_func2, NULL);    
    if(0 != res){
     	printf("create thread 2 error ! errNo : %d\n", res);        
     	exit(1);    
    }
    
    /* 连接线程,等待线程结束 */
    res = pthread_join(thread1, NULL);    
    if (0 != res){        
    	printf("thread1 pthread_join error, errorNo: %d\n", res);        
    	return 0;    
    }
    res = pthread_join(thread2, NULL);    
    if (0 != res){        
    	printf("thread2 pthread_join error, errorNo: %d\n", res);        
    	return 0;    
    }
    return 0;
}

参考资料
线程属性: https://www.cnblogs.com/zengkefu/p/5683957.html(墙裂推荐)
线程取消:https://www.cnblogs.com/lijunamneg/archive/2013/01/25/2877211.html
线程连接和线程分离:
https://zhuanlan.zhihu.com/p/97418361
https://www.runoob.com/cplusplus/cpp-multithreading.html
避免僵尸线程的四种方法:
https://blog.csdn.net/bobbypollo/article/details/79891451



这篇关于结合源码的Linux线程基础详细整理的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程