1.线程的概念
为了进一步减少处理器的空转时间,支持多处理器以及减少上下文切换开销,进程在演化中出现了另外一个概念线程。
它是进程内独立的一条运行路线,是内核调度的最小单元,也被称为轻量级的进程。
以前对线程和进程真的是傻傻分不清,面试中也经常会出现线程和进程的区别之类的问题。其实也比较好区分。
从上图我们可以看出线程和进程的关系,图片上看感觉线程是进程的子集?其实线程属于轻量级的进程,假如我们的qq是一个进程,那么里面的聊天功能,打字功能等子模块也需要一个个进程来完成吗??显然是可以完成的,但是感觉有点大材小用,切换时系统开销肯定是很大的,用户体验也会非常的差,那么就出现了线程。
同一进程中的线程共享相同的地址空间。当然线程有共有的部分,也有自己私有的部分(TCB 堆栈 寄存器等)。
2.线程的特点:
1> 大大提高了任务的切换效率
2> 避免了额外的TLB & cache的刷新。那什么是cache,它其实就是一个高速内存,它很快到但是它也很小,cpu存取要先从cache中找,当没有的时候cpu会通过TLB(内存映射),将内存中的数据放入cache中,如果进程较大,就需要cache频繁切换,效率下降。
3.进程资源占用
一个进程中可以有多个线程,线程之间有一些资源是共享的 ,就像进程一样,有私有的也有自己独有的部分。
一般共享以下资源:
可执行的指令,静态数据,进程中打开的文件描述符,当前工作目录,用户ID,用户组ID
私有资源部分:
线程ID (TID),PC(程序计数器)和相关寄存器,堆栈,错误号 (errno),优先级,执行状态和属性
需要使用线程库,pthread线程库中提供了如下基本操作
1.创建线程
#include
int pthread_create(pthread_t *thread, const
pthread_attr_t *attr, void *(*routine)(void *), void *arg);
例:
void *funct(void* arg){
printf("this is thread ");
sleep(5);
}
int main(){
int re,i;
pthread_t tid;
for(i=0;i<100;i++){
re = pthread_create(&tid, NULL,funct, NULL);
pthread_detch(tid); //后面会讲,用于回收
if(re!=0){
printf("pthread_create:%s\n",strerror(re));
exit(0);
}
}
sleep(1); //需要加延时,不然进程就结束了,线程得不到运行也跟着退出了
}
注:进程结束,线程也会跟着结束。
ps -eLf|grep cthread //来查看进程中的线程
多线程程序中,任何一个线程使用exit(0)都会导致整个进程结束。
2.回收线程
线程不回收就会出现僵尸线程,同样也要回收。
先查看线程ID
然后top -p 2933(进程号) 查看内存占用。
#include
int pthread_join(pthread_t thread, void **retval);
if (pthread_create(&tid, NULL, thread_func, NULL) != 0){
printf(“fail to pthread_create”); exit(-1);
}
re = pthread_join(tid,NULL);
if(re!=0){
printf("pthread_join:%s\n",strerror(re));
return;
}
}
这样就完成指定线程的释放,此时的第二个参数下面会用到。
3.结束线程
void pthread_exit(void *retval); 返回值,可被pthread_join回收,通常配合join使用
void *funct(void* arg){
int ret;
printf("this is funct thread\n");
sleep(1);
ret = 5;
pthread_exit((void*)ret); //线程退出,将参数返回
}
int main(){
int re,i;
pthread_t tid;
re = pthread_create(&tid, NULL,funct, (void *)i);
void *retval;
pthread_join(tid,&retval);// 获取exit的返回参数
printf("retval=%d\n",(int)retval);
}
4.线程取消
int pthread_cancel(pthread_t thread);
本质上给线程发一个信号,这个使用前要确保线程是能被取消属性,并且是不是在cancel点(阻塞函数 sleep等)上此时才能取消成功.
还有一些配合使用的函数:
1》 int pthread_setcancelstate(int state, int *oldstate); 设置线程属性是可以被取消还是不可以,如果设置了不可取消属性,调用pthread_cancel,也不能被取消。
例:pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);//设置为不可取消
2》int pthread_setcanceltype(int type, int *oldtype);设置时延时取消还是立即取消
如果为延时取消,就运行到cancel点时取消,阻塞函数,sleep等都是cancel点.
如果没有cancel点呢??可以人为的加一个void pthread_testcancel(void);通过这个函数。立即取消不需要cancel点。
5.修改detach属性
pthread_detach,改变为detach属性后,不需要再用join进行回收,感觉这个更方便。
re = pthread_create(&tid, NULL,funct, (void *)i);
pthread_detach(tid);
我们在同一个进程中创建了多个线程,他们共享地址空间,通过全局变量就可以进行数据的交互,当时多个线程访问共享资源的时候就需要同步和互斥,和实时系统中是一样的。以前我们在实时系统中都知道用信号量进行操作,互斥信号量进行互斥,这里都是一样的。
在linux中它有更洋气的名字叫原子操作。
操作方式:
1.初始化
2.P操作(申请资源)
3.V操作(释放资源)
原理上没啥区别。
posix中定义了两类信号量:
无名信号量(基于内存的信号量)只用于线程间同步和互斥
有名信号量,保存在文件中,可以用于线程也可以用于进程的同步和互斥
1.线程的同步
int sem_init(sem_t *sem, int pshared, unsigned int value);
int sem_wait(sem_t *sem); // P操作
int sem_post(sem_t *sem); // V操作
例:
#define NUM 64
sem_t sem;
char buf[NUM];
void * writeBuff(void * arg){
while(1){
fgets(buf,NUM,stdin);
sem_post(&sem); //P操作
}
}
void *readBuff(void * arg){
while(1){
sem_wait(&sem); //V操作
printf("buf=%s\n",buf);
memset(buf,0,NUM);
}
}
int main(){
pthread_t tid1,tid2;
int re;
sem_init(&sem,0,0);//初始化信号量
re = pthread_create(&tid1, NULL,writeBuff, NULL);
if(re!=0){
printf("pthread_create:%s\n",strerror(re));
exit(0);
}
re = pthread_create(&tid2, NULL,readBuff, NULL);
if(re!=0){
printf("pthread_create:%s\n",strerror(re));
exit(0);
}
while(1){
sleep(1);
}
}
2.线程的互斥
mutex互斥锁,和我们平时用的也没啥区别,也是一种信号量
任务访问临界资源前申请锁,访问完后释放锁
int pthread_mutex_init(pthread_mutex_t *mutex,const pthread_mutexattr_t * attr);
int pthread_mutex_lock(pthread_mutex_t *mutex);申请锁
int pthread_mutex_unlock(pthread_mutex_t *mutex);释放锁
FILE *fp;
pthread_mutex_t mutex;
void *write1(void* arg){
int a=0;
a = (int)arg;
int len,i;
char *c1 = "Hello world\n";
char *c2;
len = strlen(c1);
int td = pthread_self();
pthread_detach(pthread_self());
c2 = c1;
while(1){
pthread_mutex_lock(&mutex);//上锁
for(i=0;i
注:我们在使用线程函数的时候要注意,我们要使用线程库,在编译的时候也要注意要链接上库,不然编译会报错
gcc -o pthread pthread.c -lpthread 详细的可以查看库的生成那一节