Linux学习之多线程编程(线程的基本控制)

言之者无罪,闻之者足以戒。 ——《诗序》

二、线程的基本控制

1、终止进程:

如果进程中的任意一个进程调用了exit、_exit、_Exit,那么整个进程就会终止

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

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

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

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

下面我们写一个程序用一下三种返回方式:

#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <errno.h>
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 failure\n");
                return 0;
        }
        sleep(1);
        printf("main thread\n");
        return 0;
}

2、线程连接:

(1)pthread_join线程连接函数

扫描二维码关注公众号,回复: 3708624 查看本文章

int pthread_join(pthread_t tid,void **rval)

第一个参数:tid 就是指定线程的id

 第二个参数:rval就是指定线程的返回码,如果线程被取消,那么rval被置为PTHREAD_CANCELED

返回值:调用成功返回0,失败返回错误码

调用该函数的线程会一直阻塞,直到指定的线程tid调用pthread_exit,从启动例程返回或者被取消;调用该函数会使指定的线程处于分离状态,如果指定的线程已经处于分离状态,那么调用就会失败。

(2)pthread_detach分离线程函数

int pthread_detach(pthread_t thread)

参数:线程的id

返回值:成功返回0,失败返回错误码

调用该函数可以分离一个线程,线程也可以自己分离自己。

下面来看一下程序:

#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <errno.h>
void *thread_fun1(void *arg)
{
        printf("I am thread 1\n");
        return (void *)1;
}
void *thread_fun2(void *arg)
{
        printf("I am thread 2\n");
        pthread_detach(pthread_self());
        pthread_exit((void *)2);
}
int main()
{
        int err1,err2;
        pthread_t tid1,tid2;
        void *rval1,*rval2;

        err1=pthread_create(&tid1,NULL,thread_fun1,NULL);
        err2=pthread_create(&tid2,NULL,thread_fun2,NULL);

        if(err1 || err2)
        {
                printf("creatr new thread failure\n");
                return -1;
        }
        printf("I am is main thread\n");
        printf("jion1 rval is %d\n",pthread_join(tid1,&rval1));
        printf("jion2 rval is %d\n",pthread_join(tid2,&rval2));

        printf("thread 1 exit code is%d\n",(int *)rval1);
        printf("thread 2 exit code is%d\n",(int *)rval2);
        printf("I am main thread\n");
        return 0;
}

3、线程的取消:

(1)pthread_cancel取消线程函数

int pthread_cancel(pthread_t tid)

参数:需要取消的线程的id

返回值:成功返回0,失败返回错误码

取消tid指定的线程,所谓的取消只是发送一个请求,并不意味着等待线程终止,而且发送成功也不代表线程一定会终止。该函数需要被取消线程的配合,线程在很多时候会查看自己是否有取消请求,如果有就主动退出,这个查看是否有取消的地方称为取消点。

(2)pthread_setcancelstate设置线程对取消信号的反应

int pthread_setcancelstate(int state,int *oldstate)

第一个参数:有两个值:PTHREAD_CAMCEL_ENABLE(响应取消信号)和PTHREAD_CANCEL_DISABLE(忽略取消信号)

第二个参数:如果不为NULL则是存储原来的取消状态以便恢复

返回值:成功返回0 ,失败返回错误码

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

(3)pthread_setcanceltype设置线程取消动机的执行时机

int pthread_setcanceltype(int type, int *oldtype)

第一个参数:有两个值:PTHREAD_CANCEL_DEFERRED(收到取消信号后继续运行至下一个取消点再退出)和PTHREAD_CANCEL_ASYNCHRONOUS(收到取消信号后立即退出)。注意:这两个参数是在线程响应取消信号的时候才有作用(PTHREAD_CAMCEL_ENABLE

第二个参数:oldtype如果不设置为NULL则存储运来的取消动作类型值。

返回值:成功返回0,失败返回错误码

取消类型,是线程对取消信号的响应方式,立即取消或者延时取消。线程创建时默认是延时取消。 

下面先看一下程序的框架:

程序代码:

#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <errno.h>
void *thread_fun(void *arg)
{
        int stateval;
        int typeval;
        stateval=pthread_setcancelstate(PTHREAD_CANCEL_DISABLE,NULL);
        if(stateval != 0)
        {
                printf("set cancel state failure\n");
        }
        printf("I am xiaoyi\n");
        sleep(4);

        printf("about to cancel\n");
        stateval=pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,NULL);
        if(stateval != 0)
        {
                printf("set cancel state failure\n");
        }
        typeval=pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED,NULL);
        if(typeval != 0)
        {
                printf("set cancel type failure\n");
        }

        printf("first cancel point\n");
        printf("second cancel point\n");
        return (void *)10;
}
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 failure\n");
                return -1;
        }
        sleep(2);

        cval=pthread_cancel(tid);
        if(cval != 0)
        {
                printf("cancel thread failure\n");
        }
        jval=pthread_join(tid,&rval);

        printf("new thread exit code is %d\n",(int *)rval);
        return 0;
}

4、向线程发送信号:

(1)pthread_kill发送信号函数

int pthread_kill(pthread_t thread,int sig)

第一个参数:线程的id

第二个参数:要发送的信号

返回值:成功返回0,失败返回错误码

作用:向指定ID的线程发送sig信号,如果线程代码内不做处理,则按照信号默认的行为影响整个进程,也就是说,如果你给一个线程发送了SIGQUIT信号,但线程却没有实现signal处理函数,则整个进程会退出

如果第二个参数不是0 ,那就一定要清楚到底要干什么,而且一定要实现线程的信号处理,否则就会影响整个进程;如果第二个参数是0 ,这就是一个保留信号,其实并没有发送信号,作用是用来判断线程是不是还活着。

下面给出一段代码熟悉一下知识:

#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <errno.h>

void *thread_fun(void *arg)
{
//      sleep(1);
        printf("I am xiaoyi\n");

        return (void *)0;
}

int main()
{
        pthread_t tid;
        int err;
        int s;
        void *rval;
        err=pthread_create(&tid,NULL,thread_fun,NULL);
        if(err != 0)
        {
                printf("create new thread failure\n");
                return -1;
        }
        sleep(1);

        s=pthread_kill(tid,0);
//      s=pthread_kill(tid,SIGQUIT);
//      if(s == ESRCH)
//      {
//              printf("thread tid is not found\n");
//      }
        if(s != 0)
        {
                printf("thread tid is not found\n");
        }
//      pthread_join(tid,&rval);
        printf("I am main thread\n");
        return 0;
}

(2)sigaction设置一个信号的处理函数

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

第一个参数:信号的名字

第二个参数:传入新的处理方式

第三个参数:传出旧的处理方式

返回值:成功返回0,失败返回错误码

struct sigaction {
               void     (*sa_handler)(int);
               void     (*sa_sigaction)(int, siginfo_t *, void *);
               sigset_t   sa_mask;
               int        sa_flags;
               void     (*sa_restorer)(void);
           }

sa_handler:指定信号捕捉后的处理函数名,也可以赋值为SIG_IGN表示忽略或SIG_DFT表示执行默认操作

sa_mask:调用信号处理函数时,所要屏蔽的信号集合(信号屏蔽字)。注意:仅在处理函数被调用期间屏蔽生效,是临时性设置。用sigaddset函数添加需要被捕捉的信号。

 sa_flags:通常设置为0,表示使用默认属性,为0的时候,可以屏蔽正在处理的信号(若在处理二号信号的时候又有二号信号,则此时传来的二号信号就会被屏蔽)

(3)sigemptyset清空信号集

int sigemptyset(sigset_t *set)

参数:信号集

返回值:成功返回0,失败返回-1

(4)sigfillset将所有信号加入信号集

int sigfillset(sigset_t *set)

参数:信号集

返回值:成功返回0,失败返回-1

(5)sigaddset增加一个信号到信号集

int sigaddset(sigset_t *set , int signum)

第一个参数:信号集

第二个参数:要增加的信号

返回值:成功返回0,失败返回-1

(6)sigdelset将指定信号从信号集中删除

int sigdelset(sigset_t *set , int signum)

第一个参数:信号集

第二个参数:要删除的信号

返回值:成功返回0,失败返回-1

(7)pthread_sigmask在主线程中控制信号掩码

int pthread_sigmask(int show ,const sigset_t set, sigset_t *oldset)

第一个参数: SIG_BLOCK:向当前的信号掩码中添加set,其中set表示要阻塞的信号组。

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

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

第二个参数:信号组

第三个参数:以前的信号组

返回值:成功返回0 ,失败返回错误码

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

一般情况下被阻塞的信号将不能中断此线程的执行,除非此信号的产生是因为程序运行出错;如SIGSEGV,另外不能被忽略处理的信号 SIGKILL 和 SIGSTOP 也无法被阻塞。

我们写一个程序学习一下上面的这些函数:

先看一下程序的框架:

再看一下程序的代码:

#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <errno.h>

void sig_handler1(int arg)
{
        printf("thread1 get signal\n");
        return;
}
void sig_handler2(int arg)
{
        printf("thread2 get signal\n");
        return;
}
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 thread 1 failure\n");
                return ;
        }
        err = pthread_create(&tid2,NULL,thread_fun2,NULL);
        if(err != 0)
        {
                printf("create new thread 2 failure\n");
                return ;
        }

        sleep(1);

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

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

        return 0;


}

5、清除操作

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

 (1)pthread_cleanup_push注册处理程序

void pthread_cleanup_push(void (*routine)(void *), void *arg)

第一个参数:一个处理函数

第二个的参数:处理函数的入口参数

返回值:无

(2)pthread_cleanup_pop清除处理程序

void pthread_cleanup_pop(int execute)
            参数:非零即调用

返回值:无

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

1)调用pthread_exit
             2)响应取消请求
             3)用非零参数调用pthread_cleanup_pop

下面来看一下程序:

#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <errno.h>

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 *thread_fun1(void *arg)
{
        printf("new thread 1 \n");
        pthread_cleanup_push(first_clean,"xiaoyi1");
        pthread_cleanup_push(second_clean,"xiaoyi1");

        pthread_cleanup_pop(1);
        pthread_cleanup_pop(1);

        return (void *)1;
}
void *thread_fun2(void *arg)
{
        printf("new thread 2 \n");
        pthread_cleanup_push(first_clean,"xiaoyi2");
        pthread_cleanup_push(second_clean,"xiaoyi2");

        pthread_cleanup_pop(0);
        pthread_cleanup_pop(1);

        pthread_exit((void *)2);
}
int main()
{
        pthread_t tid1,tid2;
        int err;
        err =pthread_create(&tid1, NULL, thread_fun1, NULL);
        if(err != 0)
        {
                printf("create new thread 1failed\n");
                return;
        }
        err =pthread_create(&tid2, NULL, thread_fun2, NULL);
        if(err != 0)
        {
                printf("create new thread 2failed\n");
                return;
        }

        sleep(2);

        return 0;

}

到这里为止线程的基本操作就说完了。

猜你喜欢

转载自blog.csdn.net/weixin_42994525/article/details/83142095