多线程特点
线程是轻量级的进程,在Linux环境下线程的本质仍是进程。在计算机上运行的程序是一组指令及指令参数的组合,指令按照既定的逻辑控制计算机运行。操作系统会以进程为单位,分配系统资源。
可以理解为:进程是资源分配的最小单位,线程是操作系统调度的最小单位。
在概念上了解线程与进程的区别:
- 进程有自己独立的地址空间,多个线程共用一个地址空间
- 线程更加节省系统资源,效率不仅可以保持的,而且能够更高
- 在一个地址空间中多个线程独享;每个线程都有属于自己的栈区、寄存器
- 在一个地址空间中多个线程共享;代码段、堆区、全局变量区、文件描述符
- 线程是程序最小的执行单位,进程是操作系统中最小的资源分配单位
- 每个进程对应一个虚拟地址空间,一个进程只能抢一个CPU时间片
- 一个地址空间中可以划分出多个线程,在有效资源基础上,能够抢更多的CPU时间片
- 线程的CPU调度比进程快
- 上下文切换:保存上一个任务的状态,下次切换回该任务时,加载这个状态继续运行,任务从保存到再次加载就是一次上下文切换
- 线程更加廉价,启动速度更快,退出也快,对系统的冲击小
创建线程
线程函数
每个线程都有一个唯一的线程ID,ID类型为pthread_t(无符号长整型),使用pthread_self函数获取当前线程ID。
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);
// Compile and link with -pthread, 线程库的名字叫pthread, 全名: libpthread.so libptread.a
- 参数
- thread:传出参数,线程ID
- attr:线程属性
- start_routine:回调函数,函数指针
- arg:传参
- 返回值
- 创建成功返回0
线程的退出
在编写多线程程序的时候,如果想要让线程退出,但是不能导致虚拟空间的释放,可说使用线程退出函数。只要调用该函数,线程马上退出,并且不会影响其他进程正常运行,不管是在子进程还是主进程都可以使用。
#include <pthread.h>
void pthread_exit(void *retval);
- 参数:retval,线程退出时候要回传的参数
线程回收
线程函数
线程和进程是一样的,子进程退出的时候其内核资源主要有主线程回收,线程库中提供的线程回收函数叫做pthread_join,函数为阻塞函数,如果还有子线程运行,那么调用该函数会阻塞,子线程退出函数解除阻塞进行资源的回收,函数调用一次,只能回收一个子进程。
#include "pthread.h"
int pthread_join(pthread_t thread, void **retval);
- 参数:
- thread,回收的函数ID
- arr,传出参数的地址,参数由pthread_exit传出
线程分离
在某种情况下,程序中的主线程有属于自己的业余处理流程,如果让主线程负责子进程的资源回收,调用pthread_t只要子进程不退出主进程就会一直被阻塞,主线程的任务就不能被执行了。
线程库提供了线程分离函数pthread_detach,调用这个函数之后指定的子线程就可以和主线程分离,当子线程退出的时候,其占用的内核资源会被系统的其他进程接管并回收。使用线程分离之后,就无法在该进程释放子进程了。
#include "pthread.h"
int pthread_detach(pthread_t thread);
- 参数:
- read,分离的函数ID
线程取消
线程取消的意思是在特定情s况下在一个线程中杀死另一个线程,在线程A中调用线程取消函数,指定杀死线程B,此时线程B无法被杀死,在线程B中调用一次系统调用,从用户区切换到内核区,否则线程B可以一直运行。
#include <pthread.h>
int pthread_cancel(pthread_t thread);
- 参数:thread:要杀死的线程ID
线程比较函数
#include <pthread.h>
int pthread_equal(pthread_t t1, pthread_t t2);
- 返回值:相同为非0,不相等为0
示例代码
#include "stdio.h"
#include "stdlib.h"
#include "unistd.h"
#include "string.h"
#include <pthread.h>
/* 线程回调函数传参的结构体 */
typedef struct{
char name[10];
int num;
}thread_arg_t;
void *thread_callback(void *arg)
{
thread_arg_t *demo = (thread_arg_t *)arg;
// 输出自己的ID
printf("我是子线程,PID = %ld\n", pthread_self());
for(int i = 0; i < 9; i++) {
if(i == 5) {
// 定义退出是的参数,这是使用static
// 同一虚拟地址中的线程,不能共享栈区数据,但是可以共享全局数据区和堆区数据
static thread_arg_t retval;
strcpy(retval.name, demo->name);
retval.num = demo->num;
// 子线程退出
pthread_exit(&retval);
}
printf("i = %d\n", i);
}
return NULL;
}
int main()
{
pthread_t tid;
thread_arg_t arg_t;
strcpy(arg_t.name, "liustu");
arg_t.num = 20;
// 创建一个线程 参数 线程id,属性, 回调函数, 传参
pthread_create(&tid, NULL, thread_callback, &arg_t);
if(tid == 0) {
printf("创建线程成功,PID:%ld\n", tid);
printf("我是主线程,PID:%ld\n", pthread_self());
}
for(int i = 0; i < 3; i++) {
printf("i = %d\n", i);
}
// 主线程等待,让自线程运行
// sleep(2);
// 主线程调用退出函数,地址空间不会被释放
// pthread_exit(NULL);
// 创建一个指针
void *ptr = NULL;
// ptr 为传出参数,join函数内部让这个指针指向一个有效的内寸
pthread_join(tid, &ptr);
// 打印消息
thread_arg_t *demo = (thread_arg_t *)ptr;
printf("接收到子线程的数据:name %s num %d\n", demo->name, demo->num);
return 0;
}