Linux -- 多线程编程之 - 基础实现一

本文详细介绍了Linux系统中线程的基本概念,包括线程标识、线程ID的获取与比较,以及如何使用pthread库创建线程。通过示例代码展示了线程创建的过程,并强调了线程ID的唯一性和线程间资源共享的特点。同时,文章提供了简单的多线程程序示例,演示了线程创建和执行线程函数的方法。
摘要由CSDN通过智能技术生成

  所有涉及的线程相关的操作都是在用户空间中线程操作,在 Linux 系统中,pthread 是一个遵循 POSIX 标准的通用线程库,具有良好的可移植性。

  下面开始将要讨论的线程接口来自POSIX.1-2001,线程接口也称为 “pthread” 或 “POSIX线程”。

一、线程的概念

1.1、线程概述

  典型的UNIX进程可以看成只有一个控制线程:一个进程在某一时刻只能做一件事情。有了多个控制线程以后,在程序设计时就可以把进程设计成在某一时刻能够做不止一件事,每个线程处理各自独立的任务。

  每个线程都包含有表示执行环境所必需的信息,其中包括进程中标识线程的线程ID,一组寄存器值、栈、调度优先级和策略、信号屏蔽字、errno变量以及线程私有数据

  一个进程的所有信息对该进程的所有线程都是共享的,包括 可执行程序的代码程序的全局内存和堆内存 以及 文件描述符

1.2、线程标识

1.2.1、概述

  每个线程都有一个 线程ID,就像进程ID一样,进程ID 在整个操作系统中是唯一存在的,但是线程ID不同,线程ID只有在他所属的进程上下文中才有意义。

  进程IDpid_t 数据类型来表示的,pid_t 是一个非负整数。线程ID 是用 pthread_t 数据类型来表示的,Linux 系统中,在 /usr/include/ 路径下,有线程定义的头文件 pthread.h,在其前面中包含了头文件#include <bits/pthreadtypes.h>,然后在对应目录下找到此文件,可以使用指令 find . -name "pthreadtypes.h",在 Ubuntu 16.04 LTS 中,可以通过下面的方法找到文件。

Ubuntu@songshuai:/usr/include$ find . -name "pthreadtypes.h"
./x86_64-linux-gnu/bits/pthreadtypes.h

  查看文件内容,可以找到 pthread_t 的定义如下。

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

  由上面代码可以看出来,目前线程ID采用的是一个非0的长整形数据类型,但是在一起可移植的操作系统中,线程ID的实现的时候可以用一个结构来代表 pthread_t 数据类型,所以可移植的操作系统实现不能把它作为整数处理。因此必须使用一个函数来对两个 线程ID 进行比较。

1.2.2、线程ID的比较

  在目前的Linux系统中,已经提供了一个函数用来比较两个线程是否相等。

#include <pthread.h>
int pthread_equal(pthread_t t1, pthread_t t2);

Compile and link with -pthread.

功能:
  比较两个线程的线程ID是否相等
参数:
  t1 :线程1ID
  t2 :线程2ID
返回:
  若线程ID相等,返回非0数值
  若线程ID不相等,返回0

1.2.3、线程ID的获取

  pthread 是一个遵循 POSIX 标准的通用线程库,想要获取线程的ID,则可以使用下面函数获取。

#include <pthread.h>
pthread_t pthread_self(void);

Compile and link with -pthread.

功能:
  返回调用线程的线程的ID,与创建此线程的 pthread_create() 函数中返回的值相同
参数:
  无
返回:
  函数永远返回成功,返回调用线程的ID

  需要注意的是:

  线程ID只有在同一个进程中才有意义,所以在不同进程中,每个线程的 pthread_self() 可能返回是一样的。

用下面的代码进行测试,查看线程ID等,代码如下。

#include <pthread.h> /* for pthread_create */
#include <stdio.h>   /* for printf */
#include <stdlib.h>  /* for exit */
#include <unistd.h>  /* for usleep */

/* 线程的执行体函数 */
void *callback_func(void *arg)
{
    /* 延时50ms,用于确保主线程执行完成 */
    usleep(50000);
    /* 定义临时变量,存储线程ID */
    pthread_t mypid = 0;
    /* 获取线程的ID */
    mypid = pthread_self();
    /* 输出调试信息,并线程ID */
    printf("I'm thread %d, my PID = %X, my ID = %lu, HEX = %08lX, \n",
           *(int *)arg, getpid(), mypid, mypid);
}

/* 1、线程是依附于进程存在的 */
/* 2、进程本身被称为主线程,其他线程都叫做副线程*/
int main(int argc, const char *argv[])
{
    /* 定义两个变量,用于存储线程ID */
    pthread_t pthid1 = 0;
    pthread_t pthid2 = 0;
    /* 定义参数,用于区别是哪一个线程 */
    int encode1 = 1; /* 线程1,将此值赋值为1 */
    int encode2 = 2; /* 线程2,将此值赋值为2 */
    /* 输出调试语句 */
    printf("pthread ID test!\n");

    /* 创建线程 */
    if (pthread_create(&pthid1, NULL, callback_func, &encode1) != 0)
    {
        perror("pthread_create error"); /* 打印错误信息 */
        exit(EXIT_FAILURE);             /* 结束整个进程 */
    }
    /* 延时5ms,用于确保执行的顺序 */
    usleep(5000);
    /* 创建线程 */
    if (pthread_create(&pthid2, NULL, callback_func, &encode2) != 0)
    {
        perror("pthread_create error"); /* 打印错误信息 */
        exit(EXIT_FAILURE);             /* 结束整个进程 */
    }

    /* 输出调试信息,并线程ID */
    printf("I'm create caller, return pthid1 = %lu, HEX = %08lX\n", pthid1, pthid1);
    printf("I'm create caller, return pthid2 = %lu, HEX = %08lX\n", pthid2, pthid2);

    /* 阻塞等待用于输入,防止主线程运行退出 */
    getchar();

    return 0;
}

  编译上述程序并运行,程序执行的效果如下图1.1所示。

图1.1 程序执行效果图

  由上图可看出来,两个线程的进程IDPID:92)相同,但线程IDID1=7F36503D0700ID2=7F364FBC0700)不同。

  如果把线程ID看成是十进制整数,那么这两个值看起来很奇怪,但是如果把它们转化成十六进制,看起来就更合理了。

  尽管Linux线程ID是用无符号长整型来表示的,但是它们看起来像指针。

二、线程的创建

2.1、概述

  在传统UNIX进程模型中,每个进程只有一个控制线程。从概念上讲,这与基于线程的模型中每个进程只包含一个线程是相同的。在POSIX线程的情况下,程序开始运行时,它也是以单进程中的单个控制线程启动的。在创建多个控制线程以前,程序的行为与传统的进程并没有什么区别。

  创建一个新线程的函数为 pthread_create() 函数,函数原型如下所示。

#include <pthread.h>

int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
          void *(*start_routine) (void *), void *arg);

Compile and link with -pthread.

功能:
  在当前进程下开启一个新线程
参数:
  thread                     :存储线程ID,函数成功返回时,新创建线程的线程ID 会被设置成thread指向的内存单元。
  attr                           :线程的属性,一般为NULL,代表默认属性,8M的栈空间
  start_routine:线程的执行体函数
  arg                              :用于作为 start_routine 唯一的参数
返回:
  成功:返回为 0
  失败:返回一个错误码,并且 *thread 指针将无定义

说明:

  • 新创建的线程从 start_routine 函数的地址开始运行,该函数只有一个无类型指针参数 arg。如果需要向 start_routine 函数传递的参数有一个以上,那么需要把这些参数放到一个结构中,然后把这个结构的地址作为 arg 参数传入;
  • 线程创建时并不能保证新创建的线程先运行还是调用线程先运行,新创建的线程可以访问进程的地址空间,并且继承调用线程的浮点环境和信号屏蔽字,但是该线程的挂起信号集会被清除;
  • pthread 函数在调用失败时通常会返回错误码,它们并不设置 errno
  • 创建线程的函数时要指定线程指定的执行函数,线程创建之后,就开始执行相应的线程函数。在该函数运行完之后,线程结束。

2.2、代码示例

  编写如下程序,主要实现创建新线程,在线程中输出自身的线程ID,以及当前运行的计数,同时在主线程中输出创建的子线程的线程ID

  主要的代码如下所示。

#include <pthread.h> /* for pthread_create */
#include <stdio.h>	 /* for printf */
#include <stdlib.h>	 /* for exit */
#include <unistd.h>	 /* for usleep */

/* 线程的执行体函数 */
void *callback_func(void *arg)
{
	/* 延时50ms,确保主进程先输出相关信息 */
	usleep(50000); 

	/* 临时变量,用于循环计数 */
	int idx = 0;
	/* 将传入的参数进行转换 */
	int cnt = *(int *)arg;
	/* 定义临时变量,存储线程ID */
	pthread_t mypid = 0;
	/* 获取线程的ID */
	mypid = pthread_self();
	/* 输出调试信息,并线程ID */
	printf("I'm created thread, my ID = %lX\n", mypid);

	/* 开始执行线程的任务 */
	for (idx = 0; idx < cnt; idx++)
	{
		usleep(500000); /* 延时500ms */
		printf("I'm running, Cnt = %d\n", idx);
	}
}

/* 1、线程是依附于进程存在的 */
/* 2、进程本身被称为主线程,其他线程都叫做副线程*/
int main(int argc, const char *argv[])
{
	/* 定义一个变量,用于存储线程ID */
	pthread_t pthid = 0;
	/* 定义线程函数的参数 */
	int counter = 9;
	/* 输出调试语句 */
	printf("pthread create test!\n");

	/* 创建线程 */
	if (pthread_create(&pthid, NULL, callback_func, &counter) != 0)
	{
		perror("pthread_create error"); /* 打印错误信息 */
		exit(EXIT_FAILURE);				/* 结束整个进程 */
	}

	/* 输出调试信息,并线程ID */
	printf("I'm create caller, return ID = %lX\n", pthid);

	/* 阻塞等待用于输入,防止主线程运行退出 */
	getchar();

	return 0;
}

  使用到POSIX线程函数的时候,在编译的时候需要链接 -lpthread 。不然会报错相关函数未定义(显示效果如下图2.1所示)。

  编译上述程序并运行,程序执行的效果如下图2.1所示。

图2.1 程序执行效果图

  上面综述测试了线程的概要和线程的创建相关的知识点,那么关于线程的退出的问题,我们下一篇继续说明。
  好啦,废话不多说,总结写作不易,如果你喜欢这篇文章或者对你有用,请动动你发财的小手手帮忙 **点个赞**,当然 **关注一波** 那就更好了,就到这儿了,么么哒(*  ̄3)(ε ̄ *)。

上一篇:Linux – 多进程编程之 - 守护进程
下一篇:Linux – 多线程编程之 - 基础实现二

  • 14
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

青椒*^_^*凤爪爪

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值