在一个进程内部,有时不一定只有一个执行流,在多执行流下,多个执行流共享了进程的地址空间,我们把“一个程序内部的控制序列”叫做线程
在Linux下,其实没有真正意义上的线程概念,是用进程来模拟的。
Linux 的线程也叫 LWP (light weight process) 轻量级的进程,本质仍是进程(在Linux环境下)
进程和线程的区别
进程:独立地址空间,拥有PCB
线程:有独立的PCB,但没有独立的地址空间(共享)
区别:在于是否共享地址空间
Linux下:
线程:最小的执行单位
进程;最小分配资源单位,可看成是只有一个线程的进程
查看线程
ps -Lf pid // 查看该进程的线程号(区别于进程ID)
线程共享资源
线程非共享资源
6. 线程id
7. 处理器现场和栈指针(内核栈)
8. 独立的栈空间(用户空间栈)
9. errno变量
10. 信号屏蔽字
11. 调度优先级
优点:1. 提高程序的并发性 2. 开销小 3. 数据通信、共享数据方便
缺点:1. 库函数,不稳定 2. 调试、编写困难、gdb不支持 3. 对信号支持不好
优点相对突出,缺点均不是硬伤。Linux下由于实现方法导致进程、线程差别不是很大
获取线程ID,其作用对应进程中 getpid() 函数
man pthread_self // obtain ID of the calling thread
#include
pthread_t pthread_self(void);
返回值:
成功:0
失败:无!
线程ID:pthread_t 类型,本质:在Linux下为无符号整数(%lu),其他系统中可能是结构体实现
线程ID是进程内部,表示标志。(两个进程间,线程ID允许相同)
/* pthread_create.c */
#include
#include
#include
#include
#include
#include
#include
void sys_error(const char * str)
{
perror(str);
exit(1);
}
int main(int argc, char * argv [ ])
{
pthread_t tid;
tid = pthread_self();
printf("main:pid = %d, tid = %lu\n", getpid(), tid);
return 0;
}
创建一个新线程。 其作用,对应进程中fork()函数
man pthread_creat // creat a new thread
#include
int pthread_create(pthread_t* thread, const pthread_attr_t* attr, void *(*start_routine)(void*), void *arg);
返回值:
成功:0
失败:返回错误号
参数:
pthread_t 相当 typedef unsigned long int pthread_t thread: 传出参数,保存系统为我们分配好的线程ID attr:通常传NULL,表示使用线程默认属性。若想使用具体属性也可以修改该参数 start_routine:函数指针,指向线程主函数(线程体),该函数运行结束,则线程结束。 arg:线程主函数执行期间所要用到的参数
在一个线程中调用pthread_creat() 创建新的线程后,当前线程从 pthread_create() 返回继续往下执行,而新的线程所执行的代码由我们传给 pthread_create 的函数指针 start_routine 决定。start_routine 函数接收一个参数,是通过 pthread_creat 的arg参数传递给它的,该参数的类型为 void*,这个指针按什么类型解释由调用者自己定义。
/* pthread_create1.c */
#include
#include
#include
#include
#include
#include
#include
void sys_error(const char * str)
{
perror(str);
exit(1);
}
void* tfn(void * arg)
{
printf("thread:pid = %d, tid = %lu\n", getpid(), pthread_self());
return NULL;
}
int main(int argc, char * argv [ ])
{
pthread_t tid;
printf("main:pid = %d, tid = %lu\n", getpid(), pthread_self());
int ret = pthread_create(&tid, NULL, tfn, NULL);
if(ret != 0)
sys_error("pthread_create error\n");
sleep(10);
return 0;
}
#include
#include
#include
#include
#include
#include
#include
void sys_error(const char * str)
{
perror(str);
exit(1);
}
void *tfn(void* arg)
{
int i = (int)arg; // 强转
sleep(i);
printf("--I'm %dth thread:pid = %d, tid = %lu\n", i+1, getpid(), pthread_self());
return NULL;
}
int main(int argc, char * argv [])
{
int i;
int ret;
pthread_t tid;
for (i = 0 ; i < 5 ; i++)
{
ret = pthread_create(&tid, NULL, tfn, (void *)i); // 注意强转传的是i的值
if (ret != 0)
{
sys_error("pthread_creat error\n");
}
}
sleep(i);
printf("main:I'm main, pid = %d, tid = %lu\n", getpid(), pthread_self());
}
如果上述参数传递地方传的是i的地址,则会发生线程程序内调用的是main函数里面的i,调用晚的话,会出现已经i++的情况。
线程间共享全局变量
线程默认共享数据段、代码段等地址空间,常用的就是全局变量。而进程不共享全局变量,只能借助mmap。
/* glb_var.c */
#include
#include
#include
#include
int var = 100;
void *tfn(void* arg)
{
var = 200;
printf("thread, var = %d\n", var);
return NULL;
}
int main(int argc, char * argv [ ])
{
printf("At first var = %d\n", var);
pthread_t tid;
pthread_create(&tid, NULL, tfn, NULL);
sleep(1);
printf("after pthread_create, var = %d\n", var);
return 0;
}
将单个线程退出
man pthread_exit // terminate calling thread
#include
void pthread_exit(void* retval);
参数:
retval 表线程退出状态,通常传NULL
#include
#include
#include
#include
#include
#include
#include
void sys_error(const char * str)
{
perror(str);
exit(1);
}
void *tfn(void* arg)
{
int i = (int)arg;
sleep(i);
if (i == 2)
//exit(0); // exit 是退出进程,所以不打印后续线程
//return NULL; // return NULL 能实现退出线程,返回到调用者上面取
pthread_exit(NULL); // 将当前线程退出
printf("--I'm %dth thread:pid = %d, tid = %lu\n", i+1, getpid(), pthread_self());
return NULL;
}
int main(int argc, char * argv [])
{
int i;
int ret;
pthread_t tid;
for (i = 0 ; i < 5 ; i++)
{
ret = pthread_create(&tid, NULL, tfn, (void *)i);
if (ret != 0)
{
sys_error("pthread_creat error\n");
}
}
//sleep(i); // 防止主进程退出,线程未执行,所以睡一会等线程先执行
printf("main:I'm main, pid = %d, tid = %lu\n", getpid(), pthread_self());
//return 0; // 不使用return,而使用 pthread_exit退出主线程
pthread_exit(NULL);
}
错误版本:(exit 是退出进程,所以不打印后续线程)
正确版本:
注意点:
阻塞等待线程退出,获取线程退出状态 其作用,对应进程中 waitpid()函数
man pthread_join // join with a terminated thread
#include
int pthread_join(pthread_t thread, void** retval);
返回值:
成功:0
失败:错误号
参数:
thread:线程ID
retval:存储线程结束状态
调用该函数的线程将挂起等待,直到id为thread的线程终止。thread线程以不同的方法终止,通过pthread_join 得到的终止状态是不同的,总结如下:
/* pthread_join.c */
#include
#include
#include
#include
#include
#include
void sys_error(const char * str)
{
perror(str);
exit(1);
}
struct thd {
int var;
char str[256];
};
void* tfn(void* arg)
{
struct thd* tval;
tval = malloc(sizeof(tval));
tval->var =100;
strcpy(tval->str, "hello thread");
return (void*)tval;
}
/*
void* tfn(void* arg)
{
struct thd tval; // 这里之间传不行!! 因为他是一个局部变量
tval.var =100;
strcpy(tval.str, "hello thread");
return (void*)&tval; // 局部变量的地址不可做返回值
}
*/
int main(int argc, char * argv [ ])
{
pthread_t tid;
struct thd retval;
int ret = pthread_create(&tid, NULL, tfn, NULL);
if(ret != 0)
sys_error("pthread_create error\n");
ret = pthread_join(tid, (void**)&retval);
if(ret != 0)
sys_error("pthread_join error\n");
printf("child thread exit with var = %d, str= %s\n", retval->var, retval->str);
pthread_exit(NULL);
}
杀死(取消)线程 其作用对应进程中的kill
int pthread_cancel(pthread_t thread);
返回值:
成功:0
失败:错误号
/* pthread_cancel.c */
#include
#include
#include
#include
#include
#include
#include
#include
void *tfn(void* arg)
{
while(1){
printf("thread: pid = %d, tid =%lu\n", getpid(), pthread_self());
sleep(1);
}
return NULL;
}
int main(int argc, char * argv [])
{
int ret;
pthread_t tid;
ret = pthread_create(&tid, NULL, tfn, NULL);
if (ret != 0)
{
fprintf(stderr, "pthread_creat error:%s\n",strerror(ret));
exit(1);
}
printf("main:I'm main, pid = %d, tid = %lu\n", getpid(), pthread_self());
sleep(5);
ret = pthread_cancel(tid);
if (ret != 0)
{
fprintf(stderr, "pthread_creat error:%s\n",strerror(ret));
exit(1);
}
while(1);
pthread_exit(NULL);
}
子线程如果没有达到取消点,那么pthread_cancel无效,我们可以在程序中,手动添加一个取消点,pthread_testcancel();
成功被pthread_cancel()杀死的线程,返回-1,使用pthread_join 回收
/* pthread_testcancel.c */
#include
#include
#include
#include
void* tfn1(void * arg)
{
printf("thread 1 returning\n");
return (void*)111;
}
void* tfn2(void * arg)
{
printf("thread 2 exiting\n ");
pthread_exit((void*)222);
}
void* tfn3(void * arg)
{
while(1)
pthread_testcancel(); // 添加取消点,进内核取消
return (void*)6666;
}
int main(int argc, char * argv [ ])
{
pthread_t tid;
void* tret;
pthread_create(&tid, NULL, tfn1, NULL);
pthread_join(tid, &tret);
printf("thread 1 exit code = %d\n", (int)tret);
pthread_create(&tid, NULL, tfn2, NULL);
pthread_join(tid, &tret);
printf("thread 2 exit code = %d\n", (int)tret);
pthread_create(&tid, NULL, tfn3, NULL);
sleep(3);
pthread_join(tid, &tret);
printf("thread 3 exit code = %d\n", (int)tret);
return 0;
}
实现线程分离
man pthread_detch // detach a thread
#include
int pthread_detch(pthread_t thread);
参数:
thread:待分离的线程id
返回值:
成功:0
失败:errno
在线程中检查错误方法:
fprintf(stderr, "pthread_join error:%s\n", strerror(ret));
/* pthread_detach.c */
#include
#include
#include
#include
#include
#include
#include
void* tfn(void * arg)
{
printf("thread:pid = %d, tid = %lu\n", getpid(), pthread_self());
return NULL;
}
int main(int argc, char * argv [ ])
{
pthread_t tid;
printf("main:pid = %d, tid = %lu\n", getpid(), pthread_self());
int ret = pthread_create(&tid, NULL, tfn, NULL);
if(ret != 0)
{
fprintf(stderr, "pthread_create error:%s\n", strerror(ret));
exit(1);
}
ret = pthread_detach(tid);
if (ret != 0)
{
fprintf(stderr, "pthread_detach error:%s\n", strerror(ret));
exit(1);
}
sleep(1);
ret = pthread_join(tid, NULL);
if (ret != 0)
{
fprintf(stderr, "pthread_join error:%s\n", strerror(ret));
exit(1);
}
printf("main:pid = %d, tid = %lu\n", getpid(),pthread_self());
pthread_exit((void*)0);
return 0;
}
typedef struct
{
int etachstate; //线程分离状态
int schedpolicy; //线程调度策略
sturct sched_param shcedparam;
int inheritsched; //线程的继承性
int scope; // 线程的作用域
size_t guardsize; // 线程栈末尾的警戒缓冲区大小
int stackaddr_set; // 线程的栈设置
void* stackaddr; // 线程栈的位置
size_t stacksize; // 线程栈的大小
}pthread_attr_t;
主要结构体成员:
1.线程分离状态
2. 线程栈大小(默认平均分配)
3. 线程栈警戒缓冲区大小(位于栈末尾)
属性值不能直接设置,须使用相关的函数进行操作,初始化函数为 pthread_attr_init,这个函数必须在 pthread_create函数之前调用, 之后须用 pthread_attr_destroy 函数来释放资源。
线程属性主要包括:作用域(scope)、栈尺寸(stack size)、栈地址(stack address)、优先级(priority)、分离的状态(detaches state)、调度策略和参数(scheduling policy and parameters)。默认的属性为非绑定、非分离、缺省的堆栈、与父进程同样级别的优先级。
注意:应先初始化线程属性,在调用 pthread_create() 创建线程
初始化线程属性:
int pthread_attr_init(pthread_attr_t* attr);
销毁线程属性所占的系统资源:
int pthread_attr_destroy(pthread_attr_t* attr);
返回值:
成功:0
失败:errno
线程的分离状态决定一个线程以什么样的方式来终止自己
非分离状态,线程的默认属性是非分离状态,这种情况下,原有的线程等待创建的线程结束。只有当pthread_join()函数返回时,创建的线程才算终止,才能释放自己占用的系统资源
分离状态:分离线程没有被其他的线程所等待,自己运行结束了,线程也就终止了,马上释放系统资源。应该根据自己的需要,选择适当的分离状态
设置线程分离属性:分离or不分离
int pthread_attr_setdetachstate(pthread_attr_t * attr, int detachstste);
获取线程分离属性:
int pthread_attr_getdetachstate(pthread_attr_t* attr, int* detachstate);
参数:
attr:已初始化的线程属性
detachstate:
PTHREAD_CREATE_DETACHED(分离线程)
PTHREAD_CREATE_JOINABLE(非分离线程)
#include
#include
#include
#include
#include
#include
#include
void* tfn(void * arg)
{
printf("thread:pid = %d, tid = %lu\n", getpid(), pthread_self());
return NULL;
}
int main(int argc, char * argv [ ])
{
pthread_t tid;
int ret;
pthread_attr_t attr;
ret = pthread_attr_init(&attr);
if (ret != 0)
{
fprintf(stderr, "attr_init error:%s\n", strerror(ret));
exit(1);
}
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
ret = printf("main:pid = %d, tid = %lu\n", getpid(), pthread_self());
if(ret != 0)
{
fprintf(stderr, "pthread_attr_setdetachstate error:%s\n", strerror(ret));
exit(1);
}
ret = pthread_create(&tid, &attr , tfn, NULL);
if(ret != 0)
{
fprintf(stderr, "pthread_create error:%s\n", strerror(ret));
exit(1);
}
ret = pthread_attr_destroy(&attr);
if (ret != 0)
{
fprintf(stderr, "attr_destroy error:%s\n", strerror(ret));
exit(1);
}
ret = pthread_join(tid, NULL);
if (ret != 0)
{
fprintf(stderr, "pthread_join error:%s\n", strerror(ret));
exit(1);
}
printf("main:pid = %d, tid = %lu\n", getpid(),pthread_self());
pthread_exit(NULL);
}
主程序退出其他线程不退出,主线程应调用 pthread_exit
避免僵尸线程
pthread_join
pthreaad_detach
pthread_creat 指定分离属性
malloc 和 mmap 申请的内存可以被其他线程释放
应避免在多线程模型中调用 fork 除非,马上exec,子进程中只有调用fork的线程存在,其他线程在子进程中均pthread_exit
信号的复杂语义很难和多线程共存,应避免在多线程引入引号机制