Linux系统下的多线程编程入门一

本文介绍了多线程技术的优势及应用场景,重点讲解了Linux环境下基于POSIX标准的多线程编程方法,包括线程创建、等待及基本属性设置。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 引言

  线程(thread)技术早在60年代就被提出,但真正应用多线程到操作系统中去,是在80年代中期, solaris是这方面的佼佼者。传统的Unix也支持线程的概念,但是在一个进程(process)中只允许有一个线程,这样多线程就意味着多进程。现在,多线程技术已经被许多操作系统所支持,包括Windows/NT,当然,也包括Linux。

  为什么有了进程的概念后,还要再引入线程呢?使用多线程到底有哪些好处?什么的系统应该选用多线程?我们首先必须回答这些问题。

  使用多线程的理由之一是和进程相比,它是一种非常"节俭"的多任务操作方式。我们知道,在Linux系统下,启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,这是一种"昂贵"的多任务工作方式。而运行于一个进程中的多个线程,它们彼此之间使用相同的地址空间,共享大部分数据,启动一个线程所花费的空间远远小于启动一个进程所花费的空间,而且,线程间彼此切换所需的时间也远远小于进程间切换所需要的时间。

  使用多线程的理由之二是线程间方便的通信机制。对不同进程来说,它们具有独立的数据空间,要进行数据的传递只能通过通信的方式进行,这种方式不仅费时,而且很不方便。线程则不然,由于同一进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其它线程所用,这不仅快捷,而且方便。当然,数据的共享也带来其他一些问题,有的变量不能同时被两个线程所修改,有的子程序中声明为static的数据更有可能给多线程程序带来灾难性的打击,这些正是编写多线程程序时最需要注意的地方。

  除了以上所说的优点外,不和进程比较,多线程程序作为一种多任务、并发的工作方式,当然有以下的优点:

  1) 提高应用程序响应。这对图形界面的程序尤其有意义,当一个操作耗时很长时,整个系统都会等待这个操作,此时程序不会响应键盘、鼠标、菜单的操作,而使用多线程技术,将耗时长的操作(time consuming)置于一个新的线程,可以避免这种尴尬的情况。

  2) 使多CPU系统更加有效。操作系统会保证当线程数不大于CPU数目时,不同的线程运行于不同的CPU上。

  3) 改善程序结构。一个既长又复杂的进程可以考虑分为多个线程,成为几个独立或半独立的运行部分,这样的程序会利于理解和修改。

  下面我们先来尝试编写一个简单的多线程程序。

  简单的多线程编程

  Linux系统下的多线程遵循POSIX线程接口,称为pthread。编写Linux下的多线程程序,需要使用头文件pthread.h,连接时需要使用库libpthread.a。顺便说一下,Linux下pthread的实现是通过系统调用clone()来实现的。clone()是Linux所特有的系统调用,它的使用方式类似fork,关于clone()的详细情况,有兴趣的读者可以去查看有关文档说明。下面我们展示一个最简单的多线程程序 example1.c。

/* example.c*/
#include <stdio.h>
#include <pthread.h>
void thread(void)
{
 int i;
 for(i=0;i<3;i++)
  printf("This is a pthread.n");
}

int main(void)
{
 pthread_t id;
 int i,ret;
 ret=pthread_create(&id,NULL,(void *) thread,NULL);
 if(ret!=0){
  printf ("Create pthread error!n");
  exit (1);
 }
 for(i=0;i<3;i++)
  printf("This is the main process.n");
 pthread_join(id,NULL);
 return (0);
}

  我们编译此程序:

gcc example1.c -lpthread -o example1

  运行example1,我们得到如下结果:

This is the main process.
This is a pthread.
This is the main process.
This is the main process.
This is a pthread.
This is a pthread.

  再次运行,我们可能得到如下结果:

This is a pthread.
This is the main process.
This is a pthread.
This is the main process.
This is a pthread.
This is the main process.

  前后两次结果不一样,这是两个线程争夺CPU资源的结果。上面的示例中,我们使用到了两个函数,pthread_create和pthread_join,并声明了一个pthread_t型的变量。

  pthread_t在头文件/usr/include/bits/pthreadtypes.h中定义:

  typedef unsigned long int pthread_t;

  它是一个线程的标识符。函数pthread_create用来创建一个线程,它的原型为:

extern int pthread_create __P ((pthread_t *__thread, __const pthread_attr_t *__attr,void *(*__start_routine) (void *), void *__arg));

  第一个参数为指向线程标识符的指针,第二个参数用来设置线程属性,第三个参数是线程运行函数的起始地址,最后一个参数是运行函数的参数。这里,我们的函数thread不需要参数,所以最后一个参数设为空指针。第二个参数我们也设为空指针,这样将生成默认属性的线程。对线程属性的设定和修改我们将在下一节阐述。当创建线程成功时,函数返回0,若不为0则说明创建线程失败,常见的错误返回代码为EAGAIN和EINVAL。前者表示系统限制创建新的线程,例如线程数目过多了;后者表示第二个参数代表的线程属性值非法。创建线程成功后,新创建的线程则运行参数三和参数四确定的函数,原来的线程则继续运行下一行代码。

  函数pthread_join用来等待一个线程的结束。函数原型为:

  extern int pthread_join __P ((pthread_t __th, void **__thread_return));

  第一个参数为被等待的线程标识符,第二个参数为一个用户定义的指针,它可以用来存储被等待线程的返回值。这个函数是一个线程阻塞的函数,调用它的函数将一直等待到被等待的线程结束为止,当函数返回时,被等待线程的资源被收回。一个线程的结束有两种途径,一种是象我们上面的例子一样,函数结束了,调用它的线程也就结束了;另一种方式是通过函数pthread_exit来实现。它的函数原型为:

  extern void pthread_exit __P ((void *__retval)) __attribute__ ((__noreturn__));

  唯一的参数是函数的返回代码,只要pthread_join中的第二个参数thread_return不是NULL,这个值将被传递给 thread_return。最后要说明的是,一个线程不能被多个线程等待,否则第一个接收到信号的线程成功返回,其余调用pthread_join的线程则返回错误代码ESRCH。

  在这一节里,我们编写了一个最简单的线程,并掌握了最常用的三个函数pthread_create,pthread_join和pthread_exit。下面,我们来了解线程的一些常用属性以及如何设置这些属性。

修改线程的属性

  在上一节的例子里,我们用pthread_create函数创建了一个线程,在这个线程中,我们使用了默认参数,即将该函数的第二个参数设为NULL。的确,对大多数程序来说,使用默认属性就够了,但我们还是有必要来了解一下线程的有关属性。

  属性结构为pthread_attr_t,它同样在头文件/usr/include/pthread.h中定义,喜欢追根问底的人可以自己去查看。属性值不能直接设置,须使用相关函数进行操作,初始化的函数为pthread_attr_init,这个函数必须在pthread_create函数之前调用。属性对象主要包括是否绑定、是否分离、堆栈地址、堆栈大小、优先级。默认的属性为非绑定、非分离、缺省1M的堆栈、与父进程同样级别的优先级。

  关于线程的绑定,牵涉到另外一个概念:轻进程(LWP:Light Weight Process)。轻进程可以理解为内核线程,它位于用户层和系统层之间。系统对线程资源的分配、对线程的控制是通过轻进程来实现的,一个轻进程可以控制一个或多个线程。默认状况下,启动多少轻进程、哪些轻进程来控制哪些线程是由系统来控制的,这种状况即称为非绑定的。绑定状况下,则顾名思义,即某个线程固定的"绑"在一个轻进程之上。被绑定的线程具有较高的响应速度,这是因为CPU时间片的调度是面向轻进程的,绑定的线程可以保证在需要的时候它总有一个轻进程可用。通过设置被绑定的轻进程的优先级和调度级可以使得绑定的线程满足诸如实时反应之类的要求。

  设置线程绑定状态的函数为 pthread_attr_setscope,它有两个参数,第一个是指向属性结构的指针,第二个是绑定类型,它有两个取值: PTHREAD_SCOPE_SYSTEM(绑定的)和PTHREAD_SCOPE_PROCESS(非绑定的)。下面的代码即创建了一个绑定的线程。

#include <pthread.h>
pthread_attr_t attr;
pthread_t tid;

/*初始化属性值,均设为默认值*/
pthread_attr_init(&attr);
pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM);

pthread_create(&tid, &attr, (void *) my_function, NULL);

 线程的分离状态决定一个线程以什么样的方式来终止自己。在上面的例子中,我们采用了线程的默认属性,即为非分离状态,这种情况下,原有的线程等待创建的线程结束。只有当pthread_join()函数返回时,创建的线程才算终止,才能释放自己占用的系统资源。而分离线程不是这样子的,它没有被其他的线程所等待,自己运行结束了,线程也就终止了,马上释放系统资源。程序员应该根据自己的需要,选择适当的分离状态。设置线程分离状态的函数为 pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate)。第二个参数可选为PTHREAD_CREATE_DETACHED(分离线程)和 PTHREAD _CREATE_JOINABLE(非分离线程)。这里要注意的一点是,如果设置一个线程为分离线程,而这个线程运行又非常快,它很可能在 pthread_create函数返回之前就终止了,它终止以后就可能将线程号和系统资源移交给其他的线程使用,这样调用pthread_create的线程就得到了错误的线程号。要避免这种情况可以采取一定的同步措施,最简单的方法之一是可以在被创建的线程里调用 pthread_cond_timewait函数,让这个线程等待一会儿,留出足够的时间让函数pthread_create返回。设置一段等待时间,是在多线程编程里常用的方法。但是注意不要使用诸如wait()之类的函数,它们是使整个进程睡眠,并不能解决线程同步的问题。

  另外一个可能常用的属性是线程的优先级,它存放在结构sched_param中。用函数pthread_attr_getschedparam和函数 pthread_attr_setschedparam进行存放,一般说来,我们总是先取优先级,对取得的值修改后再存放回去。下面即是一段简单的例子。

#include <pthread.h>
#include <sched.h>
pthread_attr_t attr;
pthread_t tid;
sched_param param;
int newprio=20;

pthread_attr_init(&attr);
pthread_attr_getschedparam(&attr, ¶m);
param.sched_priority=newprio;
pthread_attr_setschedparam(&attr, ¶m);
pthread_create(&tid, &attr, (void *)myfunction, myarg);



内容概要:本文档详细介绍了在三台CentOS 7服务器(IP地址分别为192.168.0.157、192.168.0.158和192.168.0.159)上安装和配置Hadoop、Flink及其他大数据组件(如Hive、MySQL、Sqoop、Kafka、Zookeeper、HBase、Spark、Scala)的具体步骤。首先,文档说明了环境准备,包括配置主机名映射、SSH免密登录、JDK安装等。接着,详细描述了Hadoop集群的安装配置,包括SSH免密登录、JDK配置、Hadoop环境变量设置、HDFS和YARN配置文件修改、集群启动与测试。随后,依次介绍了MySQL、Hive、Sqoop、Kafka、Zookeeper、HBase、Spark、Scala和Flink的安装配置过程,包括解压、环境变量配置、配置文件修改、服务启动等关键步骤。最后,文档提供了每个组件的基本测试方法,确保安装成功。 适合人群:具备一定Linux基础和大数据组件基础知识的运维人员、大数据开发工程师以及系统管理员。 使用场景及目标:①为大数据平台建提供详细的安装指南,确保各组件能够顺利安装和配置;②帮助技术人员快速掌握Hadoop、Flink等大数据组件的安装与配置,提升工作效率;③适用于企业级大数据平台的建与维护,确保集群稳定运行。 其他说明:本文档不仅提供了详细的安装步骤,还涵盖了常见的配置项解释和故障排查建议。建议读者在安装过程中仔细阅读每一步骤,并根据实际情况调整配置参数。此外,文档中的命令和配置文件路径均为示例,实际操作时需根据具体环境进行适当修改。
在无线通信领域,天线阵列设计对于信号传播方向和覆盖范围的优化至关重要。本题要求设计一个广播电台的天线布局,形成特定的水平面波瓣图,即在东北方向实现最大辐射强度,在正东到正北的90°范围内辐射衰减最小且无零点;而在其余270°范围内允许出现零点,且正西和西南方向必须为零。为此,设计了一个由4个铅垂铁塔组成的阵列,各铁塔上的电流幅度相等,相位关系可自由调整,几何布置和间距不受限制。设计过程如下: 第一步:构建初级波瓣图 选取南北方向上的两个点源,间距为0.2λ(λ为电磁波波长),形成一个端射阵。通过调整相位差,使正南方向的辐射为零,计算得到初始相位差δ=252°。为了满足西南方向零辐射的要求,整体相位再偏移45°,得到初级波瓣图的表达式为E1=cos(36°cos(φ+45°)+126°)。 第二步:构建次级波瓣图 再选取一个点源位于正北方向,另一个点源位于西南方向,间距为0.4λ。调整相位差使西南方向的辐射为零,计算得到相位差δ=280°。同样整体偏移45°,得到次级波瓣图的表达式为E2=cos(72°cos(φ+45°)+140°)。 最终组合: 将初级波瓣图E1和次级波瓣图E2相乘,得到总阵的波瓣图E=E1×E2=cos(36°cos(φ+45°)+126°)×cos(72°cos(φ+45°)+140°)。通过编程实现计算并绘制波瓣图,可以看到三个阶段的波瓣图分别对应初级波瓣、次级波瓣和总波瓣,最终得到满足广播电台需求的总波瓣图。实验代码使用MATLAB编写,利用polar函数在极坐标下绘制波瓣图,并通过subplot分块显示不同阶段的波瓣图。这种设计方法体现了天线阵列设计的基本原理,即通过调整天线间的相对位置和相位关系,控制电磁波的辐射方向和强度,以满足特定的覆盖需求。这种设计在雷达、卫星通信和移动通信基站等无线通信系统中得到了广泛应用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值