什么是线程?
①进程:一个正在运行的程序,它是资源分配的最小单位。
进程中的事情需要按照一定的顺序逐个进行,那么如何让一个进程中的一些事情同时执行?
②为何又出现线程?
线程:有时又称轻量级进程,程序执行的最小单位,系统独立调度和分派CPU的基本单位,它是进程中的一个实体。一个进程中可以有多个线程,这些线程共享进程的所有资源,线程本身只包含一点必不可少的资源。
进程出现了很多弊端,一是由于进程是资源拥有者,创建、撤销与切换存在较大的时空开销,因此需要引入轻型进程;二是由于对称多处理机(SMP)出现,可以满足多个运行单位,而多个进程并行开销过大。
关于线程的一些术语
①并发
并发是指同一时刻,只能有一条指令执行,但多个进程指令被快速轮换执行,使得在宏观上具有多个进程同时执行的结果
看起来同时发生,单核。
②并行
并行是指同一时刻,有多条指令在多个处理器上同时执行。
真正的同时发生
③同步
1.同步:彼此有依赖关系的调用不应该“同时发生”,而同步就是要阻止那些“同时发生”的事情
④异步
异步的概念和同步相对,任何两个彼此独立的操作是异步的,它表明事情独立的发生。
线程的创造和生命
1.学习目标
①学会创建一个线程
②掌握ptrhead_create()的参数意义
③掌握线程的生命周期
2.重点难点
pthread_create()的参数
线程的生命周期
3.创建新线程
①线程ID
线程 | 进程 | |
标识符类型 | pthread_t | pid_t |
获取id | pthread_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;
}
执行结果如下: