线程
- 进程是资源管理的最小单位
- 线程是系统调度的最小单位
假设Linux是一个工厂,进程就是一个车间,线程就是车间里面的流水线(线程运行互不干扰,车间资源是共享的)
线程函数的接口特点?
- 由于线程函数接口都是封装在一个线程库中,所以我们看不见源码。但我们可以用
man 3 xxx
命令查看 - 所有的线程函数头文件都在
include <pthread.h>
中
系统调度和cpu调度的区别
系统调度是指操作系统决定哪一个进程可以使用cpu,当一个进程需要cpu,操作系统会将该进程加入到就绪列中,然后从就绪列中选择一个进程来使用。系统调度的目的,尽可能提高系统的吞吐量和响应时间,同时保证公平性和稳定性
cpu调度是指操作系统中决定在一个进程中哪一个线程可以使用cpu,在多线程应用程序中,一个进程包括多个线程,每个线程都可以独立运行,cpu调度的目的,尽可能的提高线程的响应时间和吞吐量同时保证公平性和稳定性
线程的创建和回收
- 如何创建一个子线程?--------->pthread_create()
#include <pthread.h>
int pthread_create(
pthread_t *thread, // 参数1: 存储线程ID的变量的地址
const pthread_attr_t *attr, // 参数2:attribute属性,普通属性填NULL
void *(*start_routine) (void *), // 参数3:线程执行的函数 void* fun(void*)
void *arg // 参数4:线程执行的函数的参数
);
// 返回值:成功返回0,失败返回错误码
Compile and link with -pthread. // 编译和链接需要--pthread库
实操:创建一个线程,同时做两件事
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
void *func(void* arg)
{
while(1)
{
printf("child-----------学到了学到了-----------\n");
sleep(1);
}
}
int main(int argc, char const *argv[])
{
pthread_t pid;
pthread_create(&pid, NULL, func, NULL);
while(1)
{
printf("main-我也学到了学到了\n");
sleep(1);
}
return 0;
}
终端显示:
情况一:主线程的运行时间 > 子线程的运行时间
主线程会与子线程一起运行,子线程结束运行,主线程继续工作,然后就执行return 0
,进程退出
情况二:主线程的运行时间 < 子线程的运行时间
主线程工作完毕,执行return 0
导致进程退出,主进程退出就会导致所有的进程都退出
练习1;验证一个进程可以同时运行多少条线程?
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <errno.h>
#include <string.h>
void *thread(void* arg)
{
while(1)
{
sleep(1);
}
}
int main()
{
int err = 0, count = 0;
pthread_t tid;
while(err == 0)
{
err = pthread_create(&tid, NULL, thread, NULL);
++count;
printf("count = %d\n", count);
}
printf("create pthread error: %s\n", strerror(errno));
printf("max create pthread num: %d\n", count);
getchar();
return 0;
}
终端显示:
32 这个系统,用户态的虚拟空间只有 3G,如果创建线程时分配的栈空间是 10M,那么一个进程最多只能创建 300 个左右的线程。
64 系统,用户态的虚拟空间大到有 128T,理论上不会受虚拟内存大小的限制,而会受系统的参数或性能限制。
练习2:验证一个进程中,线程结束后,不回收线程资源,最多能开多少条?
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <errno.h>
#include <string.h>
void *thread(void* arg)
{
printf("%d\n", *(int*)arg);
}
int main(int argc, char const *argv[])
{
int err = 0, count = 0;
int arg = 10;
pthread_t tid;
while(err == 0)
{
err = pthread_create(&tid, NULL, thread, &arg);
++count;
printf("count = %d\n", count);
}
printf("create pthread error: %s\n", strerror(errno));
printf("max create pthread num: %d\n", count);
getchar();
return 0;
}
终端显示:
如何接合一条线程?(阻塞等待子线程的退出) ------>pthread_join()
#include <pthread.h>
int pthread_join(
pthread_t thread, // 参数1:需要接合的线程号
void **retval // 参数2:存储子线程的退出值指针,如果填NULL,代表不关心子线程的退出状态
); // 返回值:成功0,失败非0
Compile and link with -pthread.
- 验证主线程和子线程工作时间的长短问题。结果是怎样?
- 情况1:主线程的工作时间长,去接合--------> pthread_join()会立马返回
- 情况2:主线程的工作时间短,去接合--------> pthread_join()会阻塞等待子线程的退出
验证:
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
void *thread(void* arg)
{
sleep(5);
printf("child pthread\n");
}
int main(int argc, char const *argv[])
{
pthread_t tid;
printf("main\n");
pthread_create(&tid, NULL, thread, NULL);
pthread_join(tid, NULL);
printf("end\n");
return 0;
}
终端显示:
打印man之后会等待5s再打印后续内容
如何退出线程---------->pthread_exit()
#include <pthread.h>
void pthread_exit(void *retval); // 参数:子线程退出值变量的地址--->这个退出值必须是全局变量
Compile and link with -pthread.
实操:
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
int pthread_status = 10;
void *thread(void* arg)
{
int cnt = 1;
while(1)
{
sleep(1);
printf("child pthread\n");
if(4 == cnt++)
{
break;
}
}
pthread_exit(&pthread_status);
}
int main(int argc, char const *argv[])
{
pthread_t tid;
printf("main\n");
pthread_create(&tid, NULL, thread, NULL);
void* p = NULL;
pthread_join(tid, &p);
printf("thread: %ld retval: %d pthread_status: %d\n", tid, *(int*)p, pthread_status);
printf("end\n");
return 0;
}
终端显示:
导致线程退出的原因有哪些?
- 当线程调用
pthread_exit()
- 例程函数返回时
- 收到取消请求
pthread_cancel()
- 任意一个线程调用
exit()
,main
函数结束
线程的属性--分离属性
-
什么是分离属性?
首先分离属性是线程的一个属性,有了分离属性的线程,不需要别的线程去接合它(自己回收资源),但虽说是分离的,但是线程结束了,该线程还是会退出。
设置了分离属性------>不需要pthread_join()
设置了非分离属性------>需要pthread_join()
------->默认创建的线程是非分离属性线程 -
如何创建分离属性的线程? ------>pthread_attr_setdetachstate()
方法1:添加一个分离属性到一个属性变量中,然后使用属性变量去创建一个线程,那么创建出来的线程就具有分离属性的线程
- 定义一个属性变量--->数据类型
pthread_attr_t attr
pthread_attr_t attr;
- 去初始化属性变量。---->
pthread_attr_init()
#include <pthread.h>
int pthread_attr_init(pthread_attr_t *attr); // 参数1:未初始化的属性变量
// 返回值:成功返回0,失败非0错误码
Compile and link with -pthread.
- 设置分离属性到属性变量中
#include <pthread.h>
int pthread_attr_setdetachstate(
pthread_attr_t *attr, // 参数1:已经初始化的属性变量
int detachstate // 参数2:------->PTHREAD_CREATE_DETACHED 分离属性
// -------->PTHREAD_CREATE_JOINABLE 非分离属性
);
int pthread_attr_getdetachstate(
const pthread_attr_t *attr,
int *detachstate
);
Compile and link with -pthread.
- 使用属性变量去创建一个新的线程
··
pthread_create(&thread,&attr,start_routine,NULL)
;
- 销毁属性变量
int pthread_attr_destroy(pthread_attr_t *attr);
//参数:attr:已经初始化的属性变量
//返回值:成功返回0
//失败非0错误码
实操:
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <string.h>
#include <sys/msg.h>
#include <stdlib.h>
void *func(void *arg)
{
for (int i = 0; i < 5; ++i)
{
sleep(1);
printf("child thread\n");
}
}
int main(int argc, char const *argv[])
{
pthread_t thread;
// 创建一条分离属性的线程
// 1.定义一个属性变量
pthread_attr_t attr;
// 2.初始化属性变量
pthread_attr_init(&attr);
// 3.设置分离属性到属性变量中
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
// 4.使用属性变量去创建一个新的线程
pthread_create(&thread, &attr, func, NULL);
// 5. 销毁属性变量
pthread_attr_destroy(&attr);
for (int i = 0; i < 3; ++i)
{
sleep(1);
printf("main thread\n");
}
int ret = pthread_join(thread, NULL);
if(0 != ret)
{
perror("join fail");
return -1;
}
return 0;
}
分析:由于线程添加了分离属性,主线程的运行时间比子线程短,这个时候使用主线程调用pthread_join函数阻塞等待回收子线程的资源是没用的,主线程执行完毕就进程结束了
思考:一个分离属性的线程退出,主线程还可以接合它吗? 不可以
一个分离属性的线程,在进程退出时,该分离属性的线程还会不会继续运行? 不会
方法2:先创建一个普通线程,然后在线程中调用一个设置为分离属性的函数,那么该线程就变成了有分离属性的线程
- 设置线程本身的分离属性------->
pthread_detach()
#include <pthread.h>
int pthread_detach(pthread_t thread);
Compile and link with -pthread.
- 获取线程的ID号------->pthread_self()
#include <pthread.h>
pthread_t pthread_self(void);
Compile and link with -pthread.
实操:
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <string.h>
#include <sys/msg.h>
#include <stdlib.h>
void *func(void *arg)
{
pthread_detach(pthread_self());
for (int i = 0; i < 5; ++i)
{
sleep(1);
printf("child pthread\n");
}
}
int main(int argc, char const *argv[])
{
pthread_t thread;
pthread_create(&thread, NULL, func, NULL);
for (int i = 0; i < 3; ++i)
{
sleep(1);
printf("main pthread\n");
}
int ret = pthread_join(thread, NULL);
if(0 != ret)
{
perror("join fail");
return -1;
}
return 0;
}
分析:线程创建后,是并发执行的,无论子线程先打印还是主线程先打印,主线程最后一次打印完成进程就结束,也不会阻塞等待子线程
练习:验证设置了分离属性的线程,在进程中最多能创建多少条? 无限制
线程的取消(杀死一个子线程)
- 一般主线程不用于处理任务,只是控制子线程状态,例如取消,接合---->pthread_cancel()主线程--->取消请求--->子线程
#include <pthread.h>
int pthread_cancel(pthread_t thread);
Compile and link with -pthread.
注意:线程收到取消请求,就是等价于提前退出
pthread_exit()
--->线程主动退出--->pthread_join()
pthread_cancel()
--->主线程给子线程发送取消请求
实操:
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <string.h>
#include <sys/msg.h>
#include <stdlib.h>
#include <errno.h>
void *func(void* arg)
{
int cnt = 10;
while(cnt--)
{
sleep(1);
printf("child pthread\n");
}
}
int main(int argc, char const *argv[])
{
pthread_t thread;
pthread_create(&thread, NULL, func, NULL);
sleep(5);
// 主线程给子线程发送取消请求
pthread_cancel(thread);
// 等待子线程退出
int ret = pthread_join(thread, NULL);
if(0 != ret)
{
printf("error\n");
}
return 0;
}
终端显示:
- 设置线程取消的状态----->pthread_
#include <pthread.h>
int pthread_setcancelstate(
int state, // 参数1:PTHREAD_CANCEL_ENABLE 能响应(线程默认的属性)
// PTHREAD_CANCEL_DISABLE 不能响应
int *oldstate // 参数2: 保留之前的状态,如果不关心,填NULL
// 返回值: 成功0,失败非0错误码
);
// 如果一个线程不能响应取消,那么在这个过程中收到取消请求,会直到这个线程能取消为止,收到取消信号才会被响应
// 如果该线程收到取消请求,没法响应,那么会阻塞到这个线程能相应为止
Compile and link with -pthread.
实操:
#include <sys/types.h>
#include <sys/ipc.h>
#include <string.h>
#include <sys/msg.h>
#include <stdlib.h>
#include <errno.h>
void *func(void* arg)
{
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
int cnt = 10;
while(cnt--)
{
sleep(1);
printf("child pthread------%d\n", cnt);
}
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
}
int main(int argc, char const *argv[])
{
pthread_t thread;
pthread_create(&thread, NULL, func, NULL);
sleep(5);
// 主线程给子线程发送取消请求
pthread_cancel(thread);
// 等待子线程退出
int ret = pthread_join(thread, NULL);
if(0 != ret)
{
printf("error\n");
}
return 0;
}
终端显示:
分析:主线程5s给子线程发送取消请求,由于子线程进入就设置不响应,直到cnt加到10,即10s后,取消了子线程的不响应。主线程发给子线程的取消请求,才得以响应
- 设置线程响应取消的类型--------->pthread_setcanceltype()
int pthread_setcanceltype(int type, int *oldtype);
// 参数1:PTHREAD_CANCEL_DEFERRED---->延迟响应,即遇到一个取消函数才会响应取消请求,线程默认属性
// PTHREAD_CANCEL_ASYNCHRONOUS------->立即响应
// 返回值:成功0,失败非0错误码
注意:线程是遇到取消点函数之后才会去响应取消
取消函数(man 7 pthreads):
- fprintf()
- fputc()
- fputs()
- sleep()
- printf()
- usleep()
给线程设置延迟响应取消请求
pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL);
给线程设置立即响应取消请求
pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
线程取消例程函数
- 什么是线程取消例程函数
当线程收到取消请求时,先不要马上响应取消函数,而先去执行一个例程函数,执行完这个函数再响应取消。 - 为什么要使用取消例程函数
为了防止线程带着一些公共资源而被取消掉 - 如何实现
压栈线程的取消例程函数------->pthread_cleanup_push()
#include <pthread.h>
void pthread_cleanup_push(
void (*routine)(void *), // 参数1:线程取消的例程函数
void *arg // 参数2:传递给取消例程函数的参数
);
Compile and link with -pthread.
弹栈线程的取消例程函数----->pthread_cleanup_pop(int execute);
void pthread_cleanup_pop(int execute); // 参数:0:删除时,直接删除
// 非0:删除时,执行一次例程函数,再删除
注意:
- 子线程收到取消请求之后会执行取消例程函数,然后执行完相应取消请求,直接退出,不会再往下进行了
- 如果子线程没有收到取消请求,而且程序已经执行到了
pthread_cleanup_pop
函数时,此函数才会执行,并且根据参数决定是否执行线程取消例程函数再退出子线程 - 这两个函数必须成对出现