线程

线程

线程的概述

线程是轻量级的进程,在 Linux环境下线程的本质仍是进程。在计算机上运行的程序是一组指令及指令参数的组合,指令按照既定的逻辑控制计算机运行。操作系统会以进程为单位,分配系统资源,可以这样理解,进程是资源分配的最小单位,线程是操作系统调度执行的最小单位。

线程和进程之间的区别:

1.线程更加节省系统资源,多个线程共用同一个地址空间。

多个线程独享的是:自己的栈区,寄存器(内核中管理)

共享的是:代码段,堆区,全局数据区,打开的文件(文件描述符)

2.线程是程序的最小执行单位,进程是操作系统中的最小的资源分配单位。

一个进程对应一个虚拟的地址空间,一个进程只能抢一个cpu时间片

一个地址空间可以划分出多个线程,在有效的资源基础上,能够抢更多的cpu时间片 CPU 的调度和切换:线程的上下文切换比进程要快的多

上下文切换:进程 / 线程分时复用 CPU 时间片,在切换之前会将上一个任务的状态进行保存,下次切换回这个任务的时候,加载这个状态继续运行,任务从保存到再次加载这个过程就是一次上下文切换。

线程更加廉价,启动速度更快,退出也快,对系统资源的冲击小。

创建线程

每个线程对应一个唯一的线程ID,ID的类型为pthread_t,无符号长整形

pthread_t pthread_self(void)//返回当前进程的ID

在一个进程中调用线程创建函数,就可得到一个子线程,和进程不同,需要给每一个创建出的线程指定一个处理函数,否则这个线程无法工作。

#include<pthread.h>
int pthread_create(pthread_t*thread,const pthread_attr_t*attr,void*(*start_routine)(void*),void*arg)
    //thread:传出参数,是无符号长整形数,线程创建成功,会将线程ID写入这个指针指向的内存中
    //attr:线程的属性,一般情况下使用默认属性即可,写NULL
    //start_routine:函数指针,创建出的子线程的处理动作,也就是该函数在子线程中运行
    //arg:作为实参传递到start_routine指针指向的函数内部
    //返回值:线程 创建成功返回0,创建失败返回对应的错误号
    //返回值类型为void*类型,参数是后面的void*类型,可以把任意类型的arg类型传递给回调函数

线程退出

#include <pthread.h>
void pthread_exit(void *retval);
//参数:线程退出的时候携带的数据,当前子线程的主线程会得到该数据。如果不需要使用,指定为 NULL
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<pthread.h>

void* callback(void*arg)
{
	for(int i=0;i<5;i++)
	{
		printf("子线程:i=%d\n",i);
	}
	printf("子线程:%ld\n",pthread_self());
	return NULL;
}
int main()
{
	pthread_t tid;
	pthread_create(&tid,NULL,callback,NULL);
	for(int i=0;i<5;i++)
	{
		printf("主线程:i=%d\n",i);
	}
	printf("主线程:%ld\n",pthread_self());
    //sleep(3);//主线程执行完毕后就会释放对应的地址空间,所以让子线程执行完毕主线程再退出
	pthread_exit(NULL); // 主线程调用退出函数退出, 地址空间不会被释放
    return 0;
}


//输出:
/*主线程:i=0
主线程:i=1
主线程:i=2
主线程:i=3
主线程:i=4
主线程:139883889375040
子线程:i=0
子线程:i=1
子线程:i=2
子线程:i=3
子线程:i=4
子线程:139883886671424*/

主线程一直在运行,执行期间创建出了子线程,说明主线程有 CPU 时间片,在这个时间片内将代码执行完毕了,主线程就退出了。子线程被创建出来之后需要抢cpu时间片, 抢不到就不能运行,如果主线程退出了, 虚拟地址空间就被释放了, 子线程就一并被销毁了。但是如果某一个子线程退出了, 主线程仍在运行, 虚拟地址空间依旧存在。

编译:

gcc+编译文件名+ -lpthread -o +目标文件名
代码如下

gcc file_Name.c -lpthread -o target_File_Name

在这里插入图片描述
可以看到就生成了一个二进制文件
要是不加的话:编译器链接不到线程库文件(动态库),需要在编译的时候通过参数指定出来,动态库名为 libpthread.so 需要使用的参数为 -l,根据规则掐头去尾最终形态应该写成:-lpthread(参数和参数值中间有空格)

线程回收

#include<pthread.h>
//这是一个阻塞函数,子线程在运行这个函数就阻塞
//子线程退出,函数解除阻塞,回收对应的子线程资源,类似于回收进程使用的函数wait()
//函数被调用一次,只能回收一个子线程,如果有多个子线程则需要循环进行回收
int pthread_join(pthread_t thread,void **retval);
//thread:要被回收的子线程的线程ID
//retval:二级指针,指向一级指针的地址,是一个传出参数,这个地址存储了pthread_exit()传递出的数据,如果不需要这个参数,可以指定为NULL
//返回值:线程回收成功返回0,回收失败返回错误号,保存了传出来的地址
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>

struct Test
{
	int num;
	int age;
};
struct Test t;
void *callback(void *arg)
{
	for (int i = 0; i < 5; i++)
	{
		printf("子线程:i=%d\n", i);
	}
	//把它定义在外面,就是一个全局变量
	t.num = 10;
	t.age = 9;
	pthread_exit(&t);//栈区内存释放,访问不到,所以要定义一个全局变量
	printf("子线程:%ld\n", pthread_self());
	return NULL;
}
int main()
{
	pthread_t tid;
	pthread_create(&tid, NULL, callback, NULL);
	for (int i = 0; i < 5; i++)
	{
		printf("主线程:i=%d\n", i);
	}
	printf("主线程:%ld\n", pthread_self());
	void *ptr; // ptr指向&t
	pthread_join(tid, &ptr);
	struct Test *pt = (struct Test *)ptr;
	printf("num=%d,age=%d\n", pt->num, pt->age);
	// pthread_join(tid,NULL);不接受子线程传出来的数据
	return 0;
}

/*
使用子线程的栈:
主线程:i=0
主线程:i=1
主线程:i=2
主线程:i=3
主线程:i=4
主线程:140355027339072
子线程:i=0
子线程:i=1
子线程:i=2
子线程:i=3
子线程:i=4
num=0,age=0这里num和age是随机数*/

/*解决方案一:使用全局变量
主线程:i=0
主线程:i=1
主线程:i=2
主线程:i=3
主线程:i=4
主线程:139814649661248
子线程:i=0
子线程:i=1
子线程:i=2
子线程:i=3
子线程:i=4
num=10,age=9*/

/*解决方案二:使用主线程的栈空间*/
struct Test
{
	int num;
	int age;
};

void *callback(void *arg)
{
	for (int i = 0; i < 5; i++)
	{
		printf("子线程:i=%d\n", i);
	}
	struct Test *t = (struct Test *)arg;
	// 把它定义在外面,就是一个全局变量
	t->num = 10;
	t->age = 9;
	pthread_exit(t); // 栈区内存释放,访问不到,所以要定义一个全局变量
	printf("子线程:%ld\n", pthread_self());
	return NULL;
}
int main()
{
	struct Test t;
	pthread_t tid;
	pthread_create(&tid, NULL, callback, &t);
	for (int i = 0; i < 5; i++)
	{
		printf("主线程:i=%d\n", i);
	}
	printf("主线程:%ld\n", pthread_self());
	void *ptr; // ptr指向&t
	pthread_join(tid, &ptr);
	printf("num=%d,age=%d\n", t.num, t.age);
	// pthread_join(tid,NULL);不接受子线程传出来的数据
}

线程分离

在某些情况下,程序中的主线程有属于自己的业务处理流程,如果让主线程负责子线程的资源回收,调用 pthread_join() 只要子线程不退出主线程就会一直被阻塞,主要线程的任务也就不能被执行了。
在线程库函数中为我们提供了线程分离函数 pthread_detach(),调用这个函数之后指定的子线程就可以和主线程分离,当子线程退出的时候,其占用的内核资源就被系统的其他进程接管并回收了。线程分离之后在主线程中使用 pthread_join() 就回收不到子线程资源了。

#include<pthread.h>
//参数就子线程的线程ID,主线程就可以和这个子线程分离了
pthread_detach(pthread_t thread);

线程取消

#include<pthread.h>
//参数是要杀死的子线程的线程ID
int pthread_cancel(pthread_t thread);

线程ID的比较

#include<pthred.h>
int pthread_equal(pthread_t t1,pthread_t t2);
//参数t1和t2是要比较的线程的线程ID
//返回值:相等返回非0值,不等返回0
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值