线程
1. 线程概念
1.1 什么是线程
线程是参与系统调度的最小单位。它被包含在进程之中,是进程中的实际运行单位。一个线程指的是进程中一个单一顺序的控制流(或者说是执行路线、执行流),一个进程中可以创建多个线程,多个线程实现并发运行,每个线程执行不同的任务。譬如某应用程序设计了两个需要并发运行的任务 task1 和 task2,可将两个不同的任务分别放置在两个线程中。
1.2 线程如何创建而来?
程序启动,被操作系统OS创建->从入口main开始执行主线程->pthread_create创建子线程
主线程的重要性体现在两方面:
⚫ 其它新的线程(也就是子线程)是由主线程创建的;
⚫ 主线程通常会在最后结束运行,执行各种清理工作,譬如回收各个子线程。
1.3 线程特点
线程是程序运行的最小单位,真正运行的是线程。
系统新创建一个进程仅仅是一个容器,包含了线程运行所需要的数据结构、环境变量等信息。
多个线程共享全部系统资源,虚拟空间,文件描述符,信号处理等
各自又有自己的调用栈,寄存器环境,线程本地存储
1.4 线程与进程
-
多进程和多线程需求分析
多进程劣势:多线程优势:
1.5 并发和并行
-
串行
-
并行
在某一个时间段上存在多个任务被多个执行单元同时在运行着
-
并发
特点:时分复用
不必等待上一个线程执行完之后再做下一个,它可以打断当前执行的任务.
在同一个执行单元上,将时间分解成不同的片段(时间片),每个任务执行一段时间,时间一到则切换执行下一个任务,依次这样轮训(交叉/交替执行),这就是并发运行
像IMX6ULL是单核,只能采用并发运行系统中的线程,而肯定不可能是串行,而事实上确实如此,并发运行依次轮询速度非常快,可以当作同时运行
2. 线程ID
- 获取线程ID函数
#include <pthread.h>
pthread_t pthread_self(void);
- 检查线程ID是否相等
#include <pthread.h>
int pthread_equal(pthread_t t1, pthread_t t2);
3. 创建线程
#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
示例:
#include <pthread.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static void *new_pthread(void *arg)
{
printf("新线程 进程pid:%d,tid:%lu\n",getpid(),pthread_self());// pthread_self返回值是unsigned long int
return (void*)0;
}
int main(){
pthread_t tid;
int ret;
ret = pthread_create(&tid, NULL, new_pthread, NULL);
if(ret){
perror("pthread error\n");
_exit(-1);
}
printf("主线程 进程pid:%d,线程ID:%lu\n",getpid(),pthread_self());
sleep(1);
exit(0);
}
4. 终止线程
方法多种:
return、pthread_exit()、pthread_cancel()
#include <pthread.h>
void pthread_exit(void *retval);
//pthread_exit()函数将终止调用它的线程
//exit,_exit,_Exit函数将终止整个进程
测试子线程exit终止整个进程:
#include <pthread.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static void *new_pthread(void *arg){
printf("starting new pthread\n");
sleep(1);
printf("end new pthread\n");
exit(0);
}
int main(){
pthread_t tid;
int ret;
ret = pthread_create(&tid, NULL, new_pthread, NULL);
if(ret){
perror("pthread_create error\n");
exit(-1);
}
sleep(3);
printf(" 子线程exit没有终止进程 \n");
exit(0);
}
测试主线程结束,子线程继续执行:
#include <pthread.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static void *new_pthread(void *arg){
printf("starting new pthread\n");
sleep(1);
printf("end new pthread\n");
//exit(0);
}
int main(){
pthread_t tid;
int ret;
ret = pthread_create(&tid, NULL, new_pthread, NULL);
if(ret){
perror("pthread_create error\n");
exit(-1);
}
sleep(1);
printf("main pthread end\n");
pthread_exit(NULL);
//printf(" 子线程exit没有终止进程 \n");
exit(0);
}
5. 回收线程
#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);
主要由主线程回收子线程的资源,如果子线程还在运行,主线程的pthread_join就会阻塞等待子线程执行完毕,每调用一次只回收一个,有多个需要循环回收。
测试程序说明了子线程调用pthread_exit函数会将参数保存,pthread_join可以子线程传出的参数提出来
#include <pthread.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/*
关于解决临时变量t无法传出问题
一、struct Time t改为全局变量
二、t在主线程创建,创建子线程过程中t传入
*/
struct Time
{
int time;
int data;
};
//struct Time t
static void *callback(void *arg){
printf("callback run\n");
struct Time *t = (struct Time*)arg;
t->time = 110;
t->data = 10;
pthread_exit(t);
}
int main(){
pthread_t tid;
int ret;
struct Time t;
ret = pthread_create(&tid, NULL,callback,&t);
if(ret)
{
perror("pthread_create");
exit(-1);
}
void *ptr;
pthread_join(tid, &ptr);
//struct Time *pt = (struct Time*)ptr;
printf("time:%d,data:%d\n",t.time,t.data);//这里用t,仍然需要join因为他会阻塞等待子线程传出参数
exit(0);
}
6. 取消线程
向指定线程发送请求要求立即终止、退出
一组线程正在执行一个运算,一旦某个线程检测到错误发生,需要其它线程退出,取消线程这项功能就派上用场了
6.1 取消一个线程
#include <pthread.h>
int pthread_cancel(pthread_t thread);
//只是提出请求,线程可以自己设置不被取消或者控制如何被取消
//被取消的话会立即退出,不会等待终止
#include <pthread.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static void *callback(void *arg){
printf("new pthread run\n");
for(;;)
sleep(1);
return (void*)0;
}
int main(){
pthread_t tid;
void *ptr;
int ret;
ret = pthread_create(&tid, NULL, callback, NULL);
if(ret){
perror("creat error\n");
exit(-1);
}
sleep(1);
ret = pthread_cancel(tid);
if(ret){
perror("cancel error\n");
exit(-1);
}
ret = pthread_join(tid, &ptr);
if(ret){
perror("jion error\n");
exit(-1);
}
printf("child thread : %ld\n",(long)ptr);//PTHREAD_CANCELED
exit(0);
}
6.2 取消状态以及类型
#include <pthread.h>
int pthread_setcancelstate(int state, int *oldstate);
/*会将调用线程的取消性状态设置为参数 state 中给定的值,并将线程之前的取消性状态保存
在参数 oldstate 指向的缓冲区中*/
int pthread_setcanceltype(int type, int *oldtype);
/*如果线程的取消性状态为 PTHREAD_CANCEL_ENABLE,那么对取消请求的处理则取决于线程的
取消性类型,该类型可以通过调用 pthread_setcanceltype()函数来设置,它的参数 type 指
定了需要设置的类型,而线程之前的取消性类型则会保存在参数 oldtype 所指向的缓冲区中*/
- pthread_setcancelstate
#include <pthread.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static void *callback(void *arg){
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
for(;;)
{
printf("new pthread run\n");
sleep(1);
}
return (void*)0;
}
int main(){
pthread_t tid;
void *ptr;
int ret;
ret = pthread_create(&tid, NULL, callback, NULL);
if(ret){
perror("creat error\n");
exit(-1);
}
sleep(1);
ret = pthread_cancel(tid);
if(ret){
perror("cancel error\n");
exit(-1);
}
ret = pthread_join(tid, &ptr);//子线程不处理取消请求,直到state状态改变
if(ret){
perror("join error\n");
exit(-1);
}
printf("child thread : %ld\n",(long)ptr);//PTHREAD_CANCELED
exit(0);
}
- pthread_setcanceltype
6.3 取消点
如果一个线程没有可取消点,那么很容易不能被取消就会出现问题
6.4 线程可取消性的检测
#include <pthread.h>
void pthread_testcancel(void);
只要线程中调用此函数,就会产生一个取消点,当有取消请求被挂起,那么就可以被取消
7. 分离线程
如果不关心线程的返回状态、回收线程资源,可以是用pthread_detach函数,在线程终止时能够自动回收线程资源并将其移除
#include <pthread.h>
int pthread_detach(pthread_t thread);
#include <pthread.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static void *callback(void *arg){
int ret;
ret = pthread_detach(pthread_self());
if(ret){
perror("detach error\n");
exit(-1);
}
printf("starr new thread\n");
sleep(2);
printf("end\n");
pthread_exit(NULL);
}
int main(){
pthread_t tid;
int ret;
ret = pthread_create(&tid, NULL, callback, NULL);
if(ret){
perror("create error\n");
exit(-1);
}
sleep(1);
void *ptr;
if(pthread_join(tid, &ptr))
{
perror("join error\n");
}
pthread_exit(NULL);
}
8. 注册线程清理处理函数
#include <pthread.h>
void pthread_cleanup_push(void (*routine)(void *), void *arg);
void pthread_cleanup_pop(int execute);
当线程执行以下动作时,清理函数栈中的清理函数才会被执行:
⚫ 线程调用 pthread_exit()退出时;
⚫ 线程响应取消请求时;
⚫ 用非 0 参数调用 pthread_cleanup_pop()
#include <pthread.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static void cleanup(void *arg){
printf("cleanup:%s\n",(char *)arg);
}
static void *callback(void *arg){
printf("新线程执行·······\n");
pthread_cleanup_push(cleanup, "one");
pthread_cleanup_push(cleanup, "two");
pthread_cleanup_push(cleanup, "three");
pthread_exit((void *)0);
pthread_cleanup_pop(0);//参数为0表示不执行清理函数
pthread_cleanup_pop(0);
pthread_cleanup_pop(0);//push必须有对应的pop
}
int main(){
pthread_t tid;
int ret;
void *ptr;
ret = pthread_create(&tid, NULL, callback, NULL);
if(ret){
perror("crest error\n");
exit(-1);
}
sleep(1);
ret = pthread_join(tid, &ptr);
if(ret){
perror("join error\n");
exit(-1);
}
printf("main pthread over :%ld",(long)ptr);
exit(0);
}
#include <pthread.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static void cleanup(void *arg){
printf("cleanup:%s\n",(char *)arg);
}
static void *callback(void *arg){
printf("新线程执行·······\n");
pthread_cleanup_push(cleanup, "one");
pthread_cleanup_push(cleanup, "two");
pthread_cleanup_push(cleanup, "three");
pthread_cleanup_pop(1);//参数为1表示移除顶层函数外,执行清理函数
printf("~~~~~~~~~~~~~~~~~~\n");
sleep(2);
pthread_exit((void *)0);
pthread_cleanup_pop(0);
pthread_cleanup_pop(0);
}
int main(){
pthread_t tid;
int ret;
void *ptr;
ret = pthread_create(&tid, NULL, callback, NULL);
if(ret){
perror("crest error\n");
exit(-1);
}
sleep(1);
ret = pthread_join(tid, &ptr);
if(ret){
perror("join error\n");
exit(-1);
}
printf("main pthread over :%ld\n",(long)ptr);
exit(0);
}
9.线程与信号
9.1 信号如何映射到线程
9.2 线程的信号掩码
#include <signal.h>
int pthread_sigmask(int how, const sigset_t *set, sigset_t *oldset);
9.3 向线程发送信号
#include <signal.h>
int pthread_kill(pthread_t thread, int sig);
#include <signal.h>
#include <pthread.h>
int pthread_sigqueue(pthread_t thread, int sig, const union sigval value)
9.4 异步信号安全函数
应用程序中涉及信号处理函数时必须要非常小心,因为信号处理函数可能会在程序执行的任意时间点被调用,从而打断主程序。接下来介绍一个概念—异步信号安全函数(async-signal-safe function)
所谓可重入,常见的情况是,程序执行都某个函数foo()时,收到信号,于是暂停目前正在执行的函数,转到信号处理函数,而这个信号处理函数的执行过程中,又恰恰也会进入到刚刚执行的函数foo(),这样便发生了所谓的重入,此时如果foo()能够正确的运行,而且处理完成之后,之前暂停的foo()也能正确运行,则说明它是可重入的。