1、线程
概念:
- 线程(Thread)是操作系统能够进行运算调度的最小单位,是进程中的一个实体,是 CPU 调度和分派的基本单位。与进程相比,线程更轻量级,线程之间可以共享进程的资源,但每个线程拥有自己的独立栈空间。线程是应用层的概念,它可以与其他线程共享同一进程的地址空间、文件描述符等资源,也可以有自己的一套资源。
- 左边是一个标准的进程,它自己拥有一套完整的资源(内存空间、文件等),这些资源全部由PCB统一管理,这一整套的数据结构,以及他们的动态变化就是一个进程
- 右边是两个PCB共享很多资源,一个PCB对应着系统中的一个任务,是系统调度的对象。系统在调度的时候并不关心这个PCB是独立的还是共享的。因此右边的进程可以获得更多的CPU资源相比左边的进程
2、线程的创建
- 1、线程例程指的是:如果线程创建成功,该线程就会立即去执行的函数
- 2、POSIX线程库所有的API接口对返回值的处理原则都是一致的:成功返回0,失败返回errno
- 3、线程属性如果为NULL,则会创建一个标准的线程
练习:创建一个标准的线程:
注意:编译时需要链接pthread的静态库gcc pthread.c -o pthread -lpthread
,否则会报错,如下:
#include "stdio.h"
#include <pthread.h>
#include "unistd.h"
void* func(void *arg)
{
while(1)
{
printf("这是一个线程\n");
sleep(1);
}
}
int main()
{
//线程ID
pthread_t pid;
//创建新的线程
pthread_create(&pid, NULL, func, NULL);
while(1)
{
printf("这是主函数\n");
sleep(1);
}
return 0;
}
3、获取和设置线程的属性
获取线程的属性:
设置线程的属性:
以上API接口都是针对线程属性的
4、初始化与销毁线程属性
头文件:
#include <pthread.h>
函数原型:
int pthread_attr_init(pthread_attr_t *attr);//初始化
int pthread_attr_destroy(pthread_attr_t *attr);//销毁该线程属性变量
参数:
attr:需要初始化或者销毁的属性变量
返回值:
成功返回:0
失败返回:1
#include <pthread.h>
// 初始化线程属性
int pthread_attr_init(pthread_attr_t *attr);
// 销毁线程属性
int pthread_attr_destroy(pthread_attr_t *attr);
5、如何设置分离属性
一条线程如果是接合的,意味着这条线程在退出时不会自动释放自身的资源,而会成为僵尸进程,同时意味着该进程的退出值可以被其他线程获取。因此不需要线程退出值的话,最好把线程设置为分离状态,以保证线程不成为僵尸进程(默认情况下线程是接合的)
线程跟进程类似,在缺省的状态下退出之后,会变成僵尸进程,并且保留退出值。其他线程可以通过相关的API接和该线程,使其资源被回收,还可以获取其退出值。
接和指定线程:
注意:
- 1、如果线程退出时没有退出值,那么retval可以指定为NULL
- 2、pthread_join()指定的线程如果还在运行,那么会阻塞等待
- 3、pthread_tryjoin()指定的线程如果还在运行,会立即出错返回
练习:
1、创建线程,再去接和该线程获取其退出值
#include "stdio.h"
#include <stdlib.h>
#include <pthread.h>
#include "unistd.h"
void* func(void *arg)
{
while(1)
{
printf("这里是线程\n");
sleep(3);
break;
}
int *p = malloc(sizeof(int));
*p = 1024;
//推出本线程,并设置返回值的地址
pthread_exit((void *)p);//返回的内存地址应该是一个堆空间的内存
return 0;
}
int main()
{
//定义线程的ID;
pthread_t pid;
//创建新线程
pthread_create(&pid, NULL, func, NULL);
int *retval;
//接合ID为pid的线程,阻塞等待其退出
pthread_join(pid, (void *)&retval);
printf("接和成功,退出值为:%d\n", *retval);
return 0;
}
2、设置该线程为分离属性,看看能否在接合该线程
#include "stdio.h"
#include "pthread.h"
#include "unistd.h"
#include "stdlib.h"
void* func(void *arg)
{
while(1)
{
printf("这是线程\n");
sleep(3);
break;
}
int *p = malloc(sizeof(int));
*p = 1024;
//线程退出值
pthread_exit((void *)p);
return 0;
}
int main()
{
pthread_attr_t attr;
//初始化线程属性
pthread_attr_init(&attr);
//设置线程为分离属性
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
pthread_t pid;
//创建线程
pthread_create(&pid, &attr, func, NULL);
int *retval;
//接合线程
if(pthread_join(pid, (void *)&retval) == 0)
{
printf("接合成功\n");
return 0;
}
else
{
printf("接合失败\n");
}
return 0;
}
如果线程被设置为分离属性之后,是不可以在去接合该线程获取退出值
6、线程的调度策略:
- SCHED_FIFO:先进先出策略,线程一直运行直到自愿放弃 CPU,适用于实时任务。
- SCHED_RR:轮转策略,与 FIFO 类似,但是线程有时间片的概念,适用于实时任务。
-
Linux默认调度策略为SCHED_OTHER
- 其静态优先级必须设置为0。
- 处于0优先级的线程,按照动态优先级被调度,而动态优先级起始于线程的nice值。每当一个线程处于就绪状态但被调度器无视时,其优先级会增加一个单位,这样能保证线程间竞争CPU的公平性
- 动态优先级:“动态”表示此优先级会随着线程的表现而变化。如果一条线程是CPU消耗型,这种线程就会一直粘着CPU,这样的线程的动态优先级就会被降低。另一类线程被称为IO消耗型,这类线程大部分时间都在睡眠,调度器就会慢慢提高它的优先级,让他有更加高的响应速度。
获取和设置线程栈和警戒区的大小:
- 如果发现一条线程的栈可能会溢出,那么也许需要使用该函数来增大栈空间,但事实上不需要这么做。因为警戒区指的是没有任何访问权限的内存,用来保护相邻的两个线程的栈空间不会相互践踏
7、发送请求给线程
在某个时刻不能等待线程自然死亡,而需要勒令其马上结束,此时可以给线程发送一个取消请求,让其中断执行退出。
-
当线程收到一个取消请求时,它的表现将会取决于两个东西:
-
1、当前取消的状态:
- PTHREAD_CANCEL_ENABLE 使能 (允许取消。默认值)
- PTHREAD_CANCEL_DISABLE 失能 (不允许取消)
-
2、当前取消的类型:
- 延时响应:等待线程遇到取消点时响应取消请求
- 立即响应
注册取消函数:
练习:
#include "stdio.h"
#include "stdlib.h"
#include "pthread.h"
#include "unistd.h"
#include <string.h>
void test(void * arg )
{
printf("收到取消请求, 参数arg : %d \n" , *(int *)arg);
}
void* func(void *arg)
{
int *p = malloc(sizeof(int));
*p = 1024;
pthread_cleanup_push(test, p);
while(1)
{
printf("这里是线程\n");
sleep(3);
break;
}
//线程退出处理函数弹出 0不执行 非01执行
pthread_cleanup_pop(0);
//推出本线程设置退出值
pthread_exit((void *)p);
}
int main()
{
//创建线程
pthread_t pid;
pthread_create(&pid, NULL, func, NULL);
//退出线程
sleep(1);
pthread_cancel(pid);
printf("等待线程退出\n");
int *retval;
if(pthread_join(pid, (void *)&retval))
{
printf("结合失败\n");
return 0;
}
printf("结合成功");
return 0;
}
以上是关于线程的基本概念、创建、属性设置、调度策略以及取消操作的介绍和示例代码。