Linux:线程控制知识点总结


线程控制概念

线程控制的接口都是库函数(操作系统并没有向用户提供一个线程(轻量级进程)的接口,因此大佬们才封装的一套线程控制接口,这套封装的线程库函数,提供的线程的各种操作)

使用库函数创建一个线程(用户态),本质上是在内核中创建一个轻量级进程来实现程序调度

POSIX线程库:

  • 与线程有关的函数构成了一个完整的系列,绝大多数函数的名字都是以“pthread_”打头的
  • 要使用这些函数库,要通过引入头文<pthread.h>
  • 链接这些线程函数库时要使用编译器命令的“-lpthread”选项

线程创建

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

功能: 创建一个新的线程

参数:

  • thread: (输出型参数)用于获取线程id。
    通过这个id可以找到线程的描述信息,进而访问pcb(轻量级进程完成控制)
  • attr: 设置线程的属性,通常置NULL表示使用默认属性
    用于在创建线程的同时设置线程属性
  • start_routine: 线程入口函数(是个函数地址)
    创建一个线程就是为了运行这个函数,函数运行完毕,则线程退出
  • arg: 传给线程启动函数的参数
    通过线程入口函数,传递给线程的参数

返回值: 成功返回0;失败返回返回非0值-错误编码

代码示例

#include <stdio.h>		// 输入输出函数库
#include <stdlib.h> 	// exit函数
#include <unistd.h>		// sleep函数
#include <string.h> 	// strerror函数
#include <pthread.h>	// 线程函数库

void *rout(void *arg) {
    while(1) {
        printf("I'am thread 1\n");
        sleep(1);
	} 
}

int main()
{
    pthread_t tid;
    int ret = pthread_create(&tid, NULL, rout, NULL);
    if (ret != 0) {
        fprintf(stderr, "pthread_create : %s\n", strerror(ret));
        exit(EXIT_FAILURE);
    }

    while(1) {
        printf("I'am main thread\n");
		sleep(1); 
	}

	return 0;
}

查看线程信息:

(Mac终端) ps -efl: 选项的功能是查看线程(轻量级进程)信息(不加l 指的是只看进程信息,不管这个进程中有多少个轻量级进程)

  • ps 这个程序获取到内核中的各个pcb信息,然后打印出来

tid是什么?

tid是一个线程id,线程的操作句柄,准确来说这个tid是用户态线程的id,这个id其实就是线程独有的这块空间的首地址(每个线程被创建出来之后,都会开辟一块空间,存储自己的栈,自己的描述信息)

  • tid是一个无符号长整型数据(unsigned long int)

tid和pid有什么联系?

一个线程就有一个pcb,每个pcb都有一个pid - pid是一个整型数据(pid是一个轻量级进程id,是内核task_struct结构体中的id)

  • pcb->pid:轻量级进程id-也就是ps - efL看到的LWP
  • pcb->tgid:线程组id,等于主进程id(也就是外边看到的进程id)

LWP和 tgid实际上几乎用不到,因为线程的操作都是通过tid完成的

线程ID及进程地址空间布局

  • pthread_ create函数会产生一个线程ID,存放在第一个参数指向的地址中。该线程ID和前面说的线程ID不是一回事。
  • 前面学的的线程ID属于进程调度的范畴。因为线程是轻量级进程,是操作系统调度器的最小单位,所以需要一个数值来唯一表示该线程。
  • pthread_ create函数第一个参数指向一个虚拟内存单元,该内存单元的地址即为新创建线程的线程ID, 属于NPTL线程库的范畴。线程库的后续操作,就是根据该线程ID来操作线程的。
  • 线程库NPTL提供了pthread_ self函数,可以获得线程自身的ID:
pthread_t pthread_self(void);

pthread_t 到底是什么类型呢?取决于实现。对于Linux目前实现的NPTL实现而言:
pthread_t类型的线程ID,本质就是一个进程地址空间上的一个地址。

在这里插入图片描述

线程终止

如果需要只终止某个线程而不终止整个进程,可以有三种方法:

  1. 线程入口函数运行完毕,线程就会自动退出 - 在线程入口函数中调用return。

这种方法对主线程不适用,(main函数中的return相当于调用exit,退出的是进程而不是主线程)

  1. 线程可以调用pthread_ exit终止自己。

主线程退出,并不会导致进程退出,只有所有的线程都退出了,进程才会退出

  1. 一个线程可以调用pthread_ cancel终止同一进程中的另一个线程。

退出的线程是被动取消的(取消自己是一种违规操作)

  • pthread_exit函数
void pthread_exit(void *retval);

功能: 线程终止 (主动退出 )

参数:

  • retval:线程返回值(主动退出 )

返回值: 无返回值,跟进程一样,线程结束的时候无法返回到它的调用者(自身)

注意:

pthread_exit或者return返回的指针所指向的内存单元必须是全局的或者是用malloc分配的,不能在线程函数的栈上分配,因为当其它线程得到这个返回指针时线程函数已经退出了。

  • pthread_cancel函数
int pthread_cancel(pthread_t thread);

功能: 取消一个执行中的线程(被动取消)

参数:

  • thread:指定的线程id

返回值: 成功返回0;失败返回返回非0值-错误编码

线程等待

线程有一个属性,默认创建出来这个属性是joinable,处于这个属性的线程,退出之后,需要被其他线程等待获取返回值回收资源。若不等待,则会造成资源泄漏.

为什么需要线程等待?

  • 已经退出的线程,其空间没有被释放,仍然在进程的地址空间内。
  • 创建新的线程不会复用刚才退出线程的地址空间。

pthread_join函数

int pthread_join(pthread_t thread, void **value_ptr);

功能: 等待指定线程退出,获取其返回值(阻塞函数,线程没有退出则一直等待)

参数:

  • thread: 指定要等待退出的线程tid
  • value_ptr: 输出型参数,用于获取线程退出的返回值

返回值: 成功返回0;失败返回返回非0值-错误编码

调用该函数的线程将挂起等待,直到id为thread的线程终止。
thread线程以不同的方法终止,通过pthread_join得到的终止状态是不同的,总结如下:

  1. 如果thread线程通过return返回,value_ ptr所指向的单元里存放的是thread线程函数的返回值。
  2. 如果thread线程被别的线程调用pthread_ cancel异常终掉,value_ ptr所指向的单元里存放的是常数PTHREAD_CANCELED。
  3. 如果thread线程是自己调用pthread_exit终止的,value_ptr所指向的单元存放的是传给pthread_exit的参
    数。
  4. 如果对thread线程的终止状态不感兴趣,可以传NULL给value_ ptr参数。

在这里插入图片描述

示例代码:

#include <stdio.h>		// 输入输出函数库
#include <stdlib.h> 	// exit函数
#include <unistd.h>		// sleep函数
#include <string.h> 	// strerror函数
#include <pthread.h>	// 线程函数库

void *thread1(void *arg){
	printf("thread 1 returning ... \n"); 
	int *p = (int*)malloc(sizeof(int)); 
	*p = 1;
	return (void*)p;
}

void *thread2(void *arg){
	printf("thread 2 exiting ...\n"); 
	int *p = (int*)malloc(sizeof(int)); 
	*p = 2;
	pthread_exit((void*)p);
}

void *thread3(void *arg){
	while (1){ //
		printf("thread 3 is running ...\n"); 
		sleep(1);
	}
    return NULL;
}

int main(){
    pthread_t tid;
    void *ret;

    // thread 1 return
    pthread_create(&tid, NULL, thread1, NULL);
    pthread_join(tid, &ret);
    printf("thread return, thread id %lu, return code:%d\n", tid, *(int*)ret);
	free(ret);

    // thread 2 exit
    pthread_create(&tid, NULL, thread2, NULL);
    pthread_join(tid, &ret);
    printf("thread return, thread id %lu, return code:%d\n", tid, *(int*)ret);
    free(ret);

    // thread 3 cancel by other
    pthread_create(&tid, NULL, thread3, NULL);
    sleep(3);
    pthread_cancel(tid);
    pthread_join(tid, &ret);
    if (ret ==  PTHREAD_CANCELED)
        printf("thread return, thread id %lu, return code:PTHREAD_CANCELED\n", tid);
	else
		printf("thread return, thread id %lu, return code:NULL\n", tid);

	return 0;
}

运行结果:

apple@AppledeMacBook-Pro Linux % ./pthread
thread 1 returning ... 
thread return, thread id 123145552216064, return code:1
thread 2 exiting ...
thread return, thread id 123145552216064, return code:2
thread 3 is running ...
thread 3 is running ...
thread 3 is running ...
thread return, thread id 123145552216064, return code:PTHREAD_CANCELED
apple@AppledeMacBook-Pro Linux %

注意:

  • 线程的返回值是一个void *,是一个一级指针, 若要通过一个函数的参数获取一级指针,就需要传入一个一级指针变量的地址(也就是二级指针)
  • 常量与普通变量的区别 — 存储位置不同,生命周期不同

线程分离

将线程的属性从joinable设置为detach;

  • 默认情况下,新创建的线程是joinable的,线程退出后,需要对其进行pthread_join操作,否则无法释放资源,从而造成系统泄漏。
  • 如果不关心线程的返回值,又不想一直等待线程退出,joinable属性此时是一种负担。这个时候,我们将线程设置为detach属性,当线程退出时,自动释放线程资源。
int pthread_detach(pthread_t thread);	// 分离指定的线程(属性修改为detach)
  • 线程的分离可以任意地方,可以在线程入口函数中让线程分离自己
  • 也可以让创建线程在创建之后直接分离(在哪里分离完全取决于个人习惯)
pthread_detach(pthread_self());

如果本篇博文有帮助到您,请留个赞激励博主呐~~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值