线程
线程的概述
线程是轻量级的进程,在 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