35.Linux应用层开发---线程

 

一.线程的概念和使用

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  <pthread.h>

 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  <pthread.h>

 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<len;i++){
		  fputc(*c1,fp);
		  fflush(fp);
		  c1++;
		  usleep(10000);
	   }
	   pthread_mutex_unlock(&mutex);//解锁
	   c1 = c2;
	   sleep(1);
	}
}

void *write2(void* arg){
	int a=0;
    a = (int)arg;
	int len,i;
	char *c1 = "How are your\n";
	char *c2;
	c2 = c1;
	len = strlen(c1);
	int td = pthread_self();
	pthread_detach(pthread_self());
	while(1){
	   
	   pthread_mutex_lock(&mutex);//上锁	
	   for(i=0;i<len;i++){
		  fputc(*c1,fp);
		  fflush(fp);
		  c1++;
		  usleep(10000);
	   }
	   pthread_mutex_unlock(&mutex);//解锁
	   c1 = c2;
	   sleep(1);	
	}

}

int main(){
	int re,i=0;
	pthread_t tid1,tid2;
	
	fp = fopen("1.txt","w");
	if(!fp){
		perror("fopen");
		return -1;
	}
	pthread_mutex_init(&mutex,NULL);
	
	re = pthread_create(&tid1, NULL,write1, (void *)i);
	pthread_detach(tid1);
	if(re!=0){
		printf("pthread_create:%s\n",strerror(re));
		exit(0);
	}
	re = pthread_create(&tid2, NULL,write2, (void *)i);
	pthread_detach(tid2);
	if(re!=0){
		printf("pthread_create:%s\n",strerror(re));
		exit(0);
	}	
	while(1){
		sleep(1);
	}

}

注:我们在使用线程函数的时候要注意,我们要使用线程库,在编译的时候也要注意要链接上库,不然编译会报错

gcc -o pthread pthread.c -lpthread  详细的可以查看库的生成那一节

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值