51.Linux 什么是线程?

什么是线程?

①进程:一个正在运行的程序,它是资源分配的最小单位

进程中的事情需要按照一定的顺序逐个进行,那么如何让一个进程中的一些事情同时执行?

②为何又出现线程?

线程:有时又称轻量级进程,程序执行的最小单位,系统独立调度和分派CPU的基本单位,它是进程中的一个实体。一个进程中可以有多个线程,这些线程共享进程的所有资源,线程本身只包含一点必不可少的资源。

进程出现了很多弊端,一是由于进程是资源拥有者,创建、撤销与切换存在较大的时空开销,因此需要引入轻型进程;二是由于对称多处理机(SMP)出现,可以满足多个运行单位,而多个进程并行开销过大。

关于线程的一些术语

①并发

并发是指同一时刻,只能有一条指令执行,但多个进程指令被快速轮换执行,使得在宏观上具有多个进程同时执行的结果

看起来同时发生,单核。

②并行

并行是指同一时刻,有多条指令在多个处理器上同时执行。

真正的同时发生

③同步

1.同步:彼此有依赖关系的调用不应该“同时发生”,而同步就是要阻止那些“同时发生”的事情

④异步

异步的概念和同步相对,任何两个彼此独立的操作是异步的,它表明事情独立的发生。

线程的创造和生命

1.学习目标

①学会创建一个线程

②掌握ptrhead_create()的参数意义

③掌握线程的生命周期

2.重点难点

pthread_create()的参数

线程的生命周期

3.创建新线程

①线程ID

线程进程
标识符类型pthread_tpid_t
获取idpthread_self()getpid()
创建pthread_create()fork()

typedef unsigned long int pthread_t

获取线程ID:pthread_self()

一个实例:获取主线程ID。

pthread_t tid;

tid=pthread_self();

②创建线程

getpid()——获取线程ID

pthread_self()——获取线程ID

int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);

第一个参数,新线程id,创建成功系统回填

第二个参数,新线程的属性,NULL为默认属性

第三个参数,新线程到启动函数

第四个参数,传递给新线程

③实例:创建线程,打印ID

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>

//int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);
void print_id(char *s)
{
	pid_t pid;
	pthread_t tid;

	pid = getpid();
	tid = pthread_self();

	printf("%s pid is %u,tid is 0x%lx\n",s,pid,tid);
}

void *thread_fun(void *arg)
{
	print_id(arg);
	return (void *)0;
}

int main()
{
	pthread_t ntid;
	int err;
	
	err = pthread_create(&ntid,NULL,thread_fun,"new thread");
	if(err != 0)
	{
		printf("pthread_create error\n");
		return 0;
	}

	print_id("main thread : ");
	sleep(2);
	
	return 0;
}

执行结果如下:

考核目标:

如何给新线程传递多个参数

线程有几种基本状态

线程的生命周期

1.初始化线程/主线程

①主线程是随着进程的创建而创建。

②其他线程可以通过调用函数来创建,主要调用pthread_create

③请注意,新线程可能在当前线程从函数pthread_create返回之前就已经运行了,甚至新线程可能在当前线程从函数pthread_create返回之前就已经运行完毕了。

1、当c程序运行时,首先运行main函数。在线程代码中,这个特殊函数的执行流被称作初始线程或者主线程。你可以在初始线程中做任何普通线程可以做的事情。

2、主线程的特殊在于,它在main函数返回的时候,会导致进程结束,进程内所有的线程也将会结束。这可不是一个好现象,你可以在主线程中调用pthread_exit函数,这样进程就会等待所有线程结束时才终止。

3、主线程接受参数的方式是通过argc和argv,而普通的线程只有一个参数void *。

4、在绝大多数情况下,主线程在默认堆栈上运行,这个堆栈可以增长到足够的长度。而普通线程的堆栈是受限制的,一旦溢出就会产生错误。

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>

//int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);
struct student{
	int age;
	char name[20];
	char id[4];
};

void *thread_fun(void *stu)
{
	sleep(1);
	printf("student age is %i,name is %s,id is %s\n",((struct student *)stu)->age,((struct student *)stu)->name,((struct student *)stu)->id);
	return (void *)0;
}
int main(int argc,char *argv[])
{	
	pthread_t tid;
	int err;
	int *rval;

	struct student stu;
	stu.age=20;
	memcpy(stu.name,"zhangsan",20);	
	memcpy(stu.id,"777",4);
	err = pthread_create(&tid,NULL,thread_fun,(void *)(&stu));
	if(err!=0)
	{
		printf("create new thread failed\n");
		return 0;
	}
	
	int i;
	printf("main thread have %d args\n",argc);
	for(i=0;i<argc;i++)
	{
		printf("main thread args is %s\n",argv[i]);
	}

	pthread_exit(rval);

	return 0;
}

执行结果如下:

不终止进程的退出方式

普通的单个线程有以下3种方式退出,这样不会终止进程

(1)从启动例程中返回,返回值是线程的退出码。

(2)线程可以被同一进程中的其他线程取消。

(3)线程调用pthread_exit(void *rval)函数,rval是退出码。

rval是个无类型的指针,保存线程的退出码,其他线程可以通过返回码链接这个线程

return 和 pthread_exit的区别

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
//int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);

void *thread_fun(void *arg)
{
	if(strcmp("1",(char *)arg)==0)
	{
		printf("new thread return!\n");
		return (void *)1;
	}

	if(strcmp("2",(char *)arg)==0)
	{
		printf("new thread pthread_exit!\n");
		pthread_exit((void *)2);
	}

	if(strcmp("3",(char *)arg)==0)
	{
		printf("new thread exit!\n");
		exit(3);
	}
}

int main(int argc,char *argv[])
{
	int err;
	pthread_t tid;
	
	err = pthread_create(&tid,NULL,thread_fun,(void *)argv[1]);
	if(err != 0)
	{
		printf("create new thread failed\n");
		return 0;
	}

	sleep(1);
	printf("main thread\n");
	return 0;
}

exit会导致进程退出

线程链接

int pthread_join(pthread_t tid,void **rval);

调用该函数的线程会一直阻塞,直到指定的线程tid调用pthread_exit、从启动例程返回或者被取消

①参数tid:指定线程的id

②参数rval:指定线程的返回码,如果线程被取消,那么rval被置为PTHREAD_CANCELED

该函数调用成功返回0,失败返回错误码。

调用pthread_join函数会使线程分离,如果线程已经处于分离状态,那么调用失败

线程的取消

int pthread_cancle(pthread_t tid);

取消tid指定的线程,成功返回0.取消只是发生一个请求,并不意味着等待线程终止,而且发送成功也不是意味着tid一定会终止。

用于取消一个线程,它通常需要被取消线程的配合。线程在很多时候会查看自己是否有取消请求

如果有就主动退出,这些查看是否取消的地方称为取消点。

取消状态,就是线程对取消信号的处理方式,忽略或响应。线程创建时默认响应取消信号

int pthread_setcanclestate(int state,int *oldstate);

设置本线程对Cancel信号的反应,state有两种值:PTHREAD_CANCEL_ENABLE(缺省)和PTHREAD_CANCEL_DISABLE。

分别表示收到信号后设为CANCELED状态和忽略CANCEL信号继续运行;old_state如果不为NULL则存入原来的Cancel状态以便恢复。

int pthread_setcanceltype(int type,int *oldtype);
 *    设置本线程取消动作的执行时机,type由两种取值:PTHREAD_CANCEL_DEFFERED和 
 *    PTHREAD_CANCEL_ASYCHRONOUS,仅当Cancel状态为Enable时有效,分别表示收到信号后继续运行至下            
 *    一个取消点再退出和立即执行取消操作(退出);oldtype如果不为NULL则存入原来取消动作的类型。

查看取消点

man pthreads

取消类型

/*int pthread_cancel(pthread_t tid);
 *    取消tid指定的线程,成功返回0.但是取消只是发送一个请求,
 *    并不意味着等待线程终止,而且发送成功也意味着tid一定会终止
 *int pthread_setcancelstate(int state,int *oldstate);
 *    设置本线程对cancel信号的反应,state有两种值:PTHREAD_CANCEL_ENABLE和            
 *     PTHREAD_CANCEL_DISABLE,分别表示收到信号后设为CANCELED状态和忽略CANCEL信号继续运行; 
 *     old_state如果不为NULL则存入原来的Cancel状态以便恢复。
 *int pthread_setcanceltype(int type,int *oldtype);
 *    设置本线程取消动作的执行时机,type由两种取值:PTHREAD_CANCEL_DEFFERED和 
 *    PTHREAD_CANCEL_ASYCHRONOUS,仅当Cancel状态为Enable时有效,分别表示收到信号后继续运行至下            
 *    一个取消点再退出和立即执行取消操作(退出);oldtype如果不为NULL则存入原来取消动作的类型。
 *
 *
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>

void *thread_fun(void *arg)
{
	int stateval;
	int typeval;
	stateval = pthread_setcancelstate(PTHREAD_CANCEL_DISABLE,NULL);
	if(stateval != 0)
	{
		printf("set cancel state failed\n");
	}
	printf("I'm new thread\n");
	sleep(4);

	printf("about to cancel\n");
	stateval = pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,NULL);
	if(stateval!=0)
	{
		printf("set cancel state failed\n");
	}

	typeval=pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS,NULL);
	if(typeval != 0)
	{
		printf("setcanceltype failed\n");
	}

	printf("first cancel point\n");
	printf("second cancel point\n");
	return (void *)20;
}

int main()
{
	pthread_t tid;
	int err,cval,jval;
	void *rval;

	err = pthread_create(&tid,NULL,thread_fun,NULL);
	if(err != 0)
	{
		printf("create thread failed\n");
		return 0;
	}
	sleep(2);

	cval = pthread_cancel(tid);
	if(cval!=0)
	{
		printf("cancel thread failed\n");
	}

	jval = pthread_join(tid,&rval);
	
	printf("new thread exit code is %d\n",(int *)rval);

	return 0;

}

执行结果如下:

向线程发送信号

线程取消

int pthread_kill(pthread_t thread,int sig);

向指定ID的线程发送sig信号
 pthread_kill可不是kill,而是向线程发送signal。还记得signal吗,大部分signal的默认动作是终止进程的运行,所以,我们才要用signal()去抓信号并加上处理函数。如果线程代码内不做处理,则按照信号默认的行为影响整个进程,也就是说,如果你给一个线程发送了SIGQUIT,但线程却没有实现signal处理函数,则整个进程退出。pthread_kill(threadid, SIGKILL)也一样,杀死整个进程。 

如果int sig的参数不是0,那一定要清楚到底要干什么,而且一定要实现线程的信号处理函数,否则,就会影响整个进程。

如果int sig是0呢,这是一个保留信号,一个作用是用来判断线程是不是还活着。

pthread_kill的返回值:
成功:0
线程不存在:ESRCH
信号不合法:EINVAL

信号处理

进程信号处理:

sigaction函数的功能是检查或修改与指定信号相关联的处理动作(可同时两种操作)。

int sigaction(int signum,const struct sigaction *act,struct sigaction *oldact);

sigaction()会依参数signum指定的信号编号来设置该信号的处理函数。参数signum可以指定SIGKILL和SIGSTOP以外的所有信号。

如参数结构sigaction定义如下

struct sigaction {

void (*sa_handler)(int);

void (*sa_sigaction)(int, siginfo_t *, void *);

sigset_t sa_mask;

int sa_flags;

void (*sa_restorer)(void);

};

信号处理函数可以采用void (*sa_handler)(int)void (*sa_sigaction)(int, siginfo_t *, void *)。到底采用哪个要看sa_flags中是否设置了SA_SIGINFO位,如果设置了就采用void (*sa_sigaction)(int, siginfo_t *, void *),此时可以向处理函数发送附加信息;默认情况下采用void (*sa_handler)(int),此时只能向处理函数发送信号的数值。

sa_handler此参数和signal()的参数handler相同,代表新的信号处理函数,其他意义请参考signal()。

sa_mask 用来设置在处理该信号时暂时将sa_mask 指定的信号集搁置。

sa_restorer 此参数没有使用。

sa_flags 用来设置信号处理的其他相关操作,下列的数值可用。

sa_flags还可以设置其他标志:

SA_RESETHAND:当调用信号处理函数时,将信号的处理函数重置为缺省值SIG_DFL

SA_RESTART:如果信号中断了进程的某个系统调用,则系统自动启动该系统调用

SA_NODEFER :一般情况下, 当信号处理函数运行时,内核将阻塞该给定信号。但是如果设置了 SA_NODEFER标记, 那么在该信号处理函数运行时,内核将不会阻塞该信号

给信号signum设置一个处理函数,处理函数在sigaction中指定

sa_handler字段包含一个信号捕捉函数的地址

sa_mask字段说明了一个信号集,在调用该信号捕捉函数之前,这一信号集要加进进程的信号屏蔽字中。仅当从信号捕捉函数返回时再将进程的信号屏蔽字复位为原先值。

如果在程序中设置了sigaddset(&act.sa_mask,SIGQUIT);

程序在执行信号处理函数的过程中,发送ctrl+/信号,程序也不会已经退出,而是在信号处理函数执行完毕之后才会执行SIGQUIT的信号处理函数,然后程序退出。

如果不添加这项设置,则程序将会在接收到ctrl+/信号后马上执行退出,无论是否在ctrl+c的信号处理函数过程中。  

int sigemptyset(sigset_t *set);   //清空信号集

int sigfillset(sigset_t *set);         //将所有信号加入信号集

int sigaddset(sigset_t *set,int signum); //增加一个信号到信号集

加入至参数set 信号集里

int sigdelset(sigset_t *set,int signum);  //删除一个信号到信号集

多线程信号屏蔽处理

int pthread_sigmask(int how,const sigset_t *set,sigset_t *oldset);

     how = SIG_BLOCK:向当前的信号掩码中添加set,其中set表示要阻塞的信号组。

                SIG_UNBLOCK:向当前的信号掩码中添加set,其中set表示要取消阻塞的信号组。

                SIG_SETMASK:将当前的信号掩码替换为set,其中set表示新的信号掩码。

在多线程中,新线程的当前信号掩码会继承创造它的那个线程掩码

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
// int pthread_join(pthread_t thread, void **retval);

// void pthread_exit(void *retval);

// int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);

// int pthread_kill(pthread_t thread, int sig);

//int sigaction(int signum,const struct sigaction *act,struct sigaction *oldset);
//int pthread_sigmask(int how, const sigset_t *set, sigset_t *oldset);

//int sigaddset(sigset_t *set,int signum);

void sig_handler1(int arg)
{
	printf("thread1 get signal\n");
	return (void)0;
}
void sig_handler2(int arg)
{
	printf("thread2 get signal\n");
	return (void)0;
}

void *thread_fun1(void *arg)
{
	printf("new thread 1\n");
	struct sigaction act;
	memset(&act,0,sizeof(act));
	sigaddset(&act.sa_mask,SIGQUIT);
	act.sa_handler = sig_handler1;

	sigaction(SIGQUIT,&act,NULL);
	pthread_sigmask(SIG_BLOCK,&act.sa_mask,NULL);
	sleep(2);
}

void *thread_fun2(void *arg)
{
	printf("new thread 2\n");
	struct sigaction act;
	memset(&act,0,sizeof(act));
	sigaddset(&act.sa_mask,SIGQUIT);
	act.sa_handler = sig_handler2;
	sigaction(SIGQUIT,&act,NULL);

	pthread_sigmask(SIG_BLOCK,&act.sa_mask,NULL);
	sleep(2);
}

int main()
{
	pthread_t tid1,tid2;
	int err;
	int s;
	
	err = pthread_create(&tid1,NULL,thread_fun1,NULL);
	if(err!=0)
	{
		printf("create new thread1 failed\n");
		return 0;
	}

	err = pthread_create(&tid2,NULL,thread_fun2,NULL);
	if(err!=0)
	{
		printf("create new thread2 failed\n");
		return 0;
	}

	sleep(1);

	s = pthread_kill(tid1,SIGQUIT);
	if(s!=0)
	{
		printf("send signal to thread1 failed\n");
	}

	s = pthread_kill(tid2,SIGQUIT);
	if(s!=0)
	{
		printf("send signal to thread2 failed\n");
	}

	pthread_join(tid1,NULL);
	pthread_join(tid2,NULL);

	return 0;
}

清除操作

注销/销毁清理函数

线程可以安排它退出是的清理操作,这与进程的可以用atexit函数安排进程退出时需要调用的函数相似。这样的函数称为线程清理处理程序。线程可以建立多个清理处理程序,处理程序记录在线中,处理程序记录在栈中,所以这些处理程序执行的顺序与他们注册的顺序相反。

名称:

pthread_cleanup_push / pthread_cleanup_pop

功能:

线程清理处理程序

头文件:

#include <pthread.h>

函数原形:

void pthread_cleanup_push(void (*rtn)(void *),void *arg);

void pthread_cleanup_pop(int execute);

参数:

rtn 处理程序入口地址

arg 传递给处理函数的参数

返回值:

pthread_cleanup_push 为塞进即注册一个清理函数

pthread_cleanup_pop 为弹出即取消一个清理函数

如果pthread_cleanup_pop被传递0参数,则清除函数不会被调用,但是会清除处于栈顶的清理函数。

执行顺序与注册顺序相反。

pthread_cleanup_push(void (*rtn)(void *),void *args);   //注册处理程序

pthread_cleanup_pop(int excute);     //清理处理程序

这两个函数要成对的出现,否则编译无法通过。

当执行以下操作时调用清理函数,清理函数的参数由args传入。

1、调用pthread_exit

2、响应取消请求(请你来验证)

3、用非零参数调用pthread_clean_pop

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
 
// int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);
 
//void pthread_cleanup_push(void (*routine)(void *),void *arg);
//void pthread_cleanup_pop(int execute);
void *first_clean(void *arg)
{
	printf("%s first clean\n",arg);
	return (void *)0;
}
void *second_clean(void *arg)
{
	printf("%s second clean\n",arg);
	return (void *)0;
}

void *pthread_fun1(void *arg)
{
	printf("new pthread1\n");
	pthread_cleanup_push(first_clean,"thread1");
	pthread_cleanup_push(second_clean,"thread1");
	
	pthread_cleanup_pop(1);
	pthread_cleanup_pop(0);

	return (void *)1;
}
void *pthread_fun2(void *arg)
{
	printf("new pthread2\n");
	pthread_cleanup_push(first_clean,"thread2");
	pthread_cleanup_push(second_clean,"thread2");
	
	pthread_cleanup_pop(1);
	pthread_cleanup_pop(1);

	pthread_exit((void *)2);
}

int main()
{
	pthread_t tid1,tid2;
	int err;

	err = pthread_create(&tid1,NULL,pthread_fun1,NULL);
	if(err!=0)
	{
		printf("create new thread 1 failed\n");
		return;	
	}

	err = pthread_create(&tid2,NULL,pthread_fun2,NULL);
	if(err!=0)
	{
		printf("create new thread 2 failed\n");
		return;	
	}
	
	sleep(2);
	
	return 0;
}

执行结果如下:

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值