Linux系统编程之线程基础

Posted by Yannis on March 16, 2019

线程基本概念

   在UNIX平台下的线程实现机制和Windows下是不一样的,我对Windows下的线程实现机制并不了解。在UNIX平台下,线程是通过进程演化过来的。在刚开始UNIX平台下是没有相应的线程的概念,只是在后来其他平台下线程的概念开始慢慢的普及,所以UNIX为了跟上时代的潮流,基于原有的进程开发出了相应的线程。UNIX下的线程的英文名叫做LWP(light weight process)即轻量级进程。

Linux操作系统中我们查看进程的命令是

ps aux

我们可以看到这个PID就是进程号。那查看线程的命令是ps -Lf pid

ps -Lf 1

其中命令最后跟的数字是我们的进程号。注意这里的LWP是CPU认定的线程编号,也是CPU分配时间轮片的一个依据,这个LWP号和线程的ID号不一样,线程的ID号指的是线程在进程中的编号。

在UNIX中进程是最小的资源分配单位,比如创建一个进程会创建一个完整的内存地址空间给进程单独使用。而线程是一个最小的执行单位,因为我们的Linux内核指示CPU分配资源是更具PCB进程描述符来进行分配的,而我们的线程是通过进程演化过来的,一个进程通过调用线程创建函数得到子线程,然后这个进程本身也会沦陷为一个父线程,但是这两个线程都是有各自独立的进程描述符(PCB),这样CPU在分配时间轮片的时候会将线程考虑为一个最小的执行单位,也会给线程分配CPU资源。一个进程内的不同线程会共享一部分内存地址空间。

线程自己独享的内容为

线程ID、一组寄存器、栈、调度优先级和策略、信号屏蔽字、errno变量以及线程的私有数据。

线程共享的内容为

.text段、.bss段(未初始化的全局变量)、.data段(初始化的全局变量)、堆以及文件描述符、共享库

线程的优缺点

优点:1、提高程序的并发性 2、开销小 3、数据通信方便、共享数据方便缺点:1、库函数、不稳定 2、调试、编写较难、gdb不支持(建议大量使用print打印来进行相应的调试) 3、对信号支持不好(尽量不要将信号和线程搅在一起)

线程控制原语

pthread_self函数

获取线程ID,其作用对应于进程的getpid()函数

  pthread_t pthread_self(void); 该函数总是成功,成功返回线程的ID。

  线程ID:pthread_t类型,本质:在Linux中式无符号的整数(%lu),其他系统中可能是结构体的实现

  线程ID是进程内部的识别标志,在两个进程间,线程ID可以重复

pthread_create函数

创建一个新的线程,其作用对应于进程的fork()函数

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

参数:

  pthread_t:在Linux中可以理解为 typedef unsigned long int pthread_t

  参数1:因为这个pthread_t并不是一个const指针,说明这是一个传出参数,即相应的线程ID。

  参数2:我们通常传入NULL,表示使用线程的默认属性。如果想改变具体属性可以修改该参数。

  参数3:函数指针,指向线程主函数,该函数运行结束,则线程结束。

  参数4:线程主函数执行期间所使用的参数。

pthread_create函数测试

我们编写了一个叫做pthrd_create.c的函数

#include <stdio.h>

#include <pthread.h>

#include <stdlib.h>

#include <unistd.h>

void * thrd_func(void* arg){
	printf("thread id = %lu, pid = %u\n", pthread_self(), getpid());
	return NULL;
}
int main(){
	pthread_t tid;
	int ret;

	printf("In main1: thread id = %lu, pid = %u\n", pthread_self(), getpid());

	ret = pthread_create(&tid, NULL, thrd_func, NULL);
	if(ret != 0){
		printf("pthread_create error\n");
		exit(1);
	}
	
	printf("In main2: thread id = %lu, pid = %u\n", pthread_self(), getpid());
	
    sleep(1); //1
    
	return 0;
}

在Linux环境中我们如果使用了线程库来进行多线程编程,我们在编译的时候需要连接线程库,则编译命令为:

gcc pthrd_create.c -lpthread -o pthrd_create

我们运行以上程序得到结果:

在上面我们需要注意的一个问题,在代码中我们标注1的地方我们添加了一个sleep()的函数,这个函数是让主控线程睡一秒等待子线程完成打印的动作,如果我们不添加这个sleep()这个函数,则我们运行的结果为:

这是为什么呢,这里我们需要理解一点是由于子线程和父线程使用的地址空间都是相同的,如果我们的主线程函数运行完毕了会导致整块内存地址空间会被回收,因此我们的子线程就无法取运行相应的代码了。