(一).线程概念
1.线程是程序执行流的最小单元,是进程内部的执行分支,一个进程内有多个线程。
2.vfork创建子进程,在调用exec和exit(程序终止)之前,子进程会在父进程的地址空间内运行。
3.linux下没有描述线程的结构体,所以并没有真正意义的线程,进程有PCB(task_struct 结构体),而线程没有。但是在windows下有真正意义上的线程,有描述线程的结构体TCB。
4.linux下,cpu识别的PCB都称为轻量级进程(LWP)。
5.进程承担分配系统资源的基本实体,线程是cpu/操作系统调度的基本单位。
6.进程是独立性的,线程是共享的(也要私有的一部分),但每个线程都有自己的私有栈结构和私有的上下文信息。
线程图解:
(二)线程控制
1.创建线程
#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
返回值:成功返回0,失败返回错误号。
参数解析:
1.pthread_t *thread,
是一个phtread类型的一个指针,创建的新进程的id填写到thread参数所指的内存单元里。调用pthread_self()可以获得当前线程的id。
2.const pthread_attr_t *attr;
attr参数表示线程属性,我们暂且不讨论属性,所有代码都传NULL给attr参数,表示线程属性取缺省值。
3.void (*start_routine) (void );
在一个线程中调用pthread_create()创建新的线程后,当前线程从pthread_create()返回继续往下执行,而新的线程所执行的代码由我们传给pthread_create的函数指针,start_rountine决定。
start_rountine函数接收一个参数,是通过pthread_create的arg参数传递给它的,该参数类型为void*,这个指针按什么类型由调用者自己定义,start_rountine的返回值类型是void*,这个指针的含义同样有调用者自己定义。start_routine返回时,这个线程就退出了,其他线程可以调用pthread_join得到start_routine的返回值。类似父进程调用wait(2)得到子进程的退出状态。
4.void* arg:传给start_routine函数.
代码验证:
#include <stdio.h>
2 #include <stdlib.h>
3 #include <pthread.h>
4
5 pthread_t tid;
6 void* thread_run(void* _val)
7 {
8 printf("%s : pid is : %d,tid is : %d\n",(char*)_val,(int)getpid(),(unsigned long long)pthread_self());
9 return ;
10 }
11 int main()
12 {
13 pthread_t tid;
14 if (pthread_create(&tid,NULL,thread_run,"other thread run:")!= 0)
15 {
16 printf("creat thread error!\n");
17 return 0;
18 }
19 printf("main thread_run: pid is %d,tid is :%d\n",(int)getpid(),(unsigned long long)pthread_self());
20 sleep(1);
21 return 0;
22 }
结果
为什么会出现这样呢,是因为thread不是系统默认的库,编译时我们必须要加thread的静态库,在makefile里加上 -lpthread 就好了
运行结果:
2.线程终止
1.函数原型
#include <pthread.h>
int pthread_cancel(pthread_t thread);
参数为线程ID
2.函数原型
#include <pthread.h>
void pthread_exit(void *retval);
参数retval 是void*类型,其他线程可以调用pthread_join获得这个指针。
如果只终止某个线程而不终止整个进程,可以有3种方法:
1,从线程函数return;这种方法对主线程不适用,从main函数return相当于调用exit,使进程退出
2.一个线程可以调用pthread_cancel终止同一进程的另一进程。退出值为-1,也可以自己取消自己。
3,线程调用pthread_exit终止线程。
以下和进程等待一起实现这三种情况。
3.线程等待
#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);
1.返回值:
成功返回0;失败返回错误码。
2.参数解析
第一个参数为线程ID
第二个参数获得新进程的退出结果
值得注意的是,调用该函数的线程将挂起等待,直到id为thread的线程终止。现在我们验证三种终止线程
1.线程return返回,
结果如下:
2.thread线程被别的线程调用pthread_cancel异常终止。返回值-1;
结果如下:
3.thread自己调用pthread_exit终止的,
结果
(四).线程的分离与结合
在任何一个时间点上,线程是可结合的(joinable)或者是分离的(detached)。⼀一个可结合的线程能够被其他线程收回其资源和杀死。在被其他线程回收之前,它的存储器资源(例如栈)是不释放的。相反,⼀一个分离的线程是不能被其他线程回收或杀死的,它的存储器 资源在它终⽌止时由系统⾃自动释放。
一般情况下,线程终止后,其终止状态⼀一直保留到其它线程调用pthread_join获取它的状态为止。 但是线程也可以被置为detach 状态,这样的线程一旦终止就立刻回收它占用的所有资源,而不保留终止状态。不能对一个已经处于detach状态的线程调用pthread_join,这样的调将返回EINVAL。 对一个尚未detach的线程调用pthread_join或pthread_detach都可以把该线程置为detach状态,也 就是说,不能对同一线程调用两次pthread_join,或者如果已经对一个线程调用了pthread_detach就不能再调用pthread_join了。
默认情况下,线程被创建成可结合的。为了避免存储器泄漏,每个可结合线程都应该要么被显示地回收,即调用pthread_join;要么通过调用pthread_detach函数被分离。
如果一个可结合线程结束运行但没有被join,则它的状态类似于进程中的Zombie Process(僵尸进程),即还有一部分资源没有被回收,所以创建线程者应该调用pthread_join来等待线程运行结束,并可得到线程的退出代码,回收其资源。由于调用pthread_join后,如果该线程没有运行结束,调用者会被阻塞,在有些情况下我们并不希望如此。例如,在Web服务器中当主线程为每个新来的连接请求创建一个子线程进行处理的时候,主线程并不希望因为调用pthread_join而阻塞(因为还要继续处理之后到来的连接请求),这时可以在子线程中加入代码
pthread_detach(pthread_self());
或者父线程调用
pthread_detach(thread_id)//(非阻塞,可立即返回)
这将该子线程的状态设置为分离的(detached),如此一来,该线程运行结束后会自动释放所有资源.
代码如下
#include<stdio.h>
#include<pthread.h>
#include<stdlib.h>
#include <stdlib.h>
void *thread_run(void *val)
{
pthread_detach(pthread_self());
printf("%s\n",(char*)val);
return NULL;
}
int main()
{
pthread_t tid;
if(pthread_create(&tid,NULL,thread_run,"other thread_run..")!=0)//creat thread
{
printf("creat thread error!\n");
return 0;
}
int ret = 0;
sleep(1);
if(0==pthread_join(tid,NULL))//wait success
{
printf("pthread wait success\n");
ret = 0;
}
else
{
printf("pthread wait failed!\n");
ret = 1;
}
return ret;
}
结果如下:
从结果中我们可以看到在线程分离之后,不能再等待,如果等待则会出错,没分离的线程也不能等待两次,在多个线程执行中,如果一个线程挂掉,其他线程也会挂掉,所以说线程是不稳定的,但是多个进程中,如果一个进程挂掉,不会影响其他进程。(这里不做过多解释)
线程其他知识将再后面的博客中分享!