1.线程基本介绍
进程是资源分配的基本单位
线程是进行CPU任务调度的基本单位
线程所必须的信息:线程ID、寄存器值、栈、调度优先级和策略、信号屏蔽字、errno变量、线程私有数据
多线程的好处:某些线程在阻塞时,还有另外一些线程可以运行,多线程程序可以改善响应时间和吞吐量。
多线程的资源共享:一个进程的所有线程共享可执行程序的代码、程序的全局内存和堆内存、栈、文件描述符。
2.线程标识
线程标识:线程ID,pthread_t,在POSIX C的线程操作接口中,都是以pthread_t为单位
进程标识:进程ID,pid_t,标识进程
3.线程基本操作API
3.1 pthread_equal 线程比较
int pthread_equal(pthread_t tid1, pthread t tid2);
// 返回值:若相等,返回非0数值:否则,返回
3.2pthread_self 获得自身线程ID
pthread_t pthread_self(void);
// 返回值:调用线程的线程id
3.3 pthread_create 线程创建
int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr,
void (*start_rtn)(void *), void *restrict arg);
// tid:线程id,attr:线程属性,start_rtn:线程执行函数,arg:传入线程参数
// 返回值:若成功,返回0; 否则,返回错误编号
// 注:线程创建时,并不会保证是新创建的线程还是调用线程先运行。
3.4 pthread_exit线程终止
void pthread_exit(void *rval_ptr);
// rval_ptr 退出码,可以通过pthread_join访问到这个值
线程的三种退出方式(不退出进程):
(1)从启动例程中返回,返回值是线程退出码(即线程执行程序)
(2)线程可以被同一进程中其他线程取消
(3)线程调用pthread_exit
线程退出(会退出进程):
进程中任意线程调用exit 、_Exit、_exit 整个进程都会终止
3.5 pthread_join回收线程
int pthread_join(pthread t thread, void **rval_ptr);
// 返回值:若成功,返回0:否则,返回错误编号
调用线程将阻塞,直到指定线程返回,回收线程资源
(1)从线程执行程序返回,rval_ptr包含返回码,如果设置为NULL,就代表不获取返回值
(2)线程被取消,由rval_ptr指定的内存单元设置为PTHREAD_CANCELED
(3)rval_ptr也可以是复杂的结构体
3.6 phread_detach分离线程
int pthread_detach(pthread_t tid);
// 返回值:若成功,返回0;否则,返回错误编号
结合线程:pthread_join 结束线程,释放资源。
分离线程:线程退出时,自动释放资源。(不需要再pthread_join 会发生未知结果)
这里的释放资源是线程管理资源,如果是手动申请的内存,则需要手动去释放内存
3.6 线程取消与清理
3.6.1 pthread_cancle
int pthread_cancel(pthread_t tid);
// 返回值:若成功,返回0;否则,返回错误编号
发出cancle请求,直接终止线程而不等待。当线程运行到取消点(系统调用)时,则会被直接终止。
3.6.2 pthread_cleanup_push
void pthread_cleanup_push(void (*rtn)(void *), void *arg);
void pthread_cleanup pop(int execute) ; // execute为0,代表仅弹出函数而不执行 为1时代表弹出并执行
当线程执行以下动作时,会执行pthread_cleanup_push进去的线程清理函数,这个函数可以push进去多个,pop时按顺序弹出
1)调用pthread_exit
2)响应取消请求
3)用非零execute 参数调用pthread_cleanup_pop
3.6.3 pthread_cancle的陷阱
pthread_cancle非常容易引入问题,例如死锁、程序崩溃等问题。
假设线程A执行到sleep时,其他线程使用pthread_cancle将其强行退出,则已经上锁的这把锁,将永远处于上锁状态而无法释放,其他地方拿锁则会出现死锁问题。这里不光是死锁,一些共享资源,如申请的内存等,也会出现问题。
这里自己总结了几种方法处理这些问题
1)注册pthread_clean_up线程清理函数,在处理函数内释放锁、共享资源等(效率较高)
2)加锁前,使用pthread_setcancelstate,禁止被cancle,随后再重新允许cancle(需要等到一段代码执行完)
下面两个宏,分别是用方法1和方法2完成对锁的清理。
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#define USE_PTHREAD_CANCLESTATE
// #define USE_PTHREAD_CLEANUP
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
void thread_report_sd_cleanup(void *argv)
{
printf("xzxxxxxxxxxxxx thread thread_report_sd_cleanup 111\n");
pthread_mutex_t *lock = (pthread_mutex_t *)argv;
pthread_mutex_unlock(lock);
printf("xzxxxxxxxxxxxx thread thread_report_sd_cleanup 222\n");
}
void *thread_report_sd_property(void *argv)
{
printf("xzxxxxxxxxxxxx thread sd 111\n");
#ifdef USE_PTHREAD_CANCLESTATE
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
#endif
#ifdef USE_PTHREAD_CLEANUP
pthread_cleanup_push(thread_report_sd_cleanup, &mutex);
#endif
pthread_mutex_lock(&mutex);
sleep(20);
// pthread_exit(0);
pthread_mutex_unlock(&mutex);
#ifdef USE_PTHREAD_CLEANUP
pthread_cleanup_pop(0);
#endif
#ifdef USE_PTHREAD_CANCLESTATE
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
#endif
printf("xzxxxxxxxxxxxx thread sd 222\n");
// sleep(5);
printf("xzxxxxxxxxxxxx thread sd 333\n");
return NULL;
}
int main(int argc, const char** argv) {
pthread_t sd_report_t = 0;
pthread_create(&sd_report_t, NULL, thread_report_sd_property, NULL);
sleep(1);
int iRet = pthread_cancel(sd_report_t);
printf("xzxxxxxxxxxxxx thread sd ret:%d\n", iRet);
pthread_join(sd_report_t, NULL);
printf("xzxxxxxxxxxxxx thread sd done ret:%d\n", iRet);
return 0;
}