• 线程控制


    线程控制:
    
    线程属性:
        #include <pthread.h>
        int pthread_attr_init(pthread_attr_t *attr);
        int pthread_attr_destroy(pthread_attr_t *attr);
            返回值:成功0,失败,错误编号
        让线程已开始就处于分离状态,可以调用pthread_attr_setdetachstate函数把线程属性detachstate
        设置为以下两个合法值之一:PTHREAD_CREATE_DETACHED,以分离状态启动线程;
        或者PTHREAD_CREATE_JOINABLE,正常启动线程,应用程序可以获取线程的终止状态
        #include <pthread.h>
        int pthread_attr_getdetachstate(const pthread_attr_t *restrict attr,int *detachstate);
        int pthread_attr_setdetachstate(pthread_attr_t *attr,int *detachstate);
            返回值:成功0,失败,错误编号
    
        一个以分离状态创建线程的函数:
        #include <unistd.h>
        #include <stdlib.h>
        #include <stdio.h>
        #include <pthread.h>
    
        int makethread(void *(*fn)(void *),void *arg)
        {
            int err;
            pthread_t tid;
            pthread_attr_t attr;
    
            err=pthread_attr_init(&attr);
            if(err!=0)
                return(err);
            err=pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);
            if(err==0)
                err=pthread_create(&tid,&attr,fn,arg);
            pthread_attr_destroy(&attr);
            return(err);
        }
        可以在编译阶段使用_POSIX_THREAD_ATTR_STACKADDR和_POSIX_THREAD_ATTR_STACKSIZE符号
        来检查系统是否支持每一个线程栈属性。如果定义了,就说明它支持相应的线程栈属性。也可以把参数传递
        给sysconf函数,检查运行时系统对线程栈属性的支持情况。
    
        可以使用函数pthread_attr_getstack和pthread_attr_setstack对线程栈属性进行管理:
        #include <pthread.h>
        int pthread_attr_getstack(const pthread_attr_t *restrict attr,
                void **restrict stackaddr,
                size_t *restrict stacksize);
        int pthread_attr_setstack(pthread_attr_t *attr,void *stackaddr,size_t stacksize);
            返回值:成功,0,否则,错误编号
    
        stackaddr线程属性被定义为栈的最低内存地址,但这并不一定是栈的开始位置。
    
        可以通过pthread_attr_getstacksize和pthread_attr_setstacksize函数读取或者设置线程属性stacksize:
        #include <pthread.h>
        int pthread_attr_getstacksize(const pthread_attr_t *restrict attr,size_t *restrict stacksize);
        int pthread_attr_setstacksize(pthread_attr_t *attr,size_t stacksize);
            返回值:成功0,失败,返回错误编号
        设置stacksize属性时,选择的stacksize不能小于PTHREAD_STACK_MIN.
    
        线程属性guardsize控制着线程末尾之后用以避免栈溢出的扩展内存的大小。这个属性默认值是由具体实现来定义的,但
        常用值是系统页大小。可以把guardsize线程属性设置为0,不允许属性的这种特征行为发生:在这种情况下,不会提供
        警戒缓冲区。同样,如果修改了线程属性stackaddr,系统就认为我们将自己管理栈,进而是栈警戒缓冲区机制无效,这
        等同于把guardsize线程属性设置为0.
        #include <pthread.h>
        int pthread_attr_getguardsize(const pthread_attr_t *restrict attr,size_t *restrict guardsize);
        int pthread_attr_setguardsize(pthread_attr_t *attr,size_t guardsize);
            返回值:成功0,失败,返回错误编号
    
    互斥量属性:
        对于非默认属性,可以用pthread_mutexattr_init初始化pthread_mutexattr_t结构,用pthread_mutexattr_destroy来反初始化
        #include <pthread.h>
        int pthread_mutexattr_init(pthread_mutexattr_t *attr);
        int pthread_mutexattr_destroy(pthread_mutexattr_t *attr);
            返回值:成功0,失败,返回错误编号
        3个属性是:进程共享属性、健壮属性以及类型属性。
        进行共享属性是可选的:检查系统是否定义了_POSIX_THREAD_PROCESS_SHARED符号来判断这个平台是否支持进程共享这个属性,也可以把
        _SC_THREAD_PROCESS_SHARED参数传给sysconf函数进行检查.
    
        可以使用pthread_mutexattr_getshared函数查询pthread_mutexattr_t结构,得到它的进程共享属性,使用
        pthread_mutexattr_setshared函数修改进程共享属性:
        #include <pthread.h>
        int pthread_mutexattr_getshared(const pthread_mutexattr_t *restrict attr,int *restrict pshared);
        int pthread_mutexattr_setshared(pthread_mutexattr_t *attr,int pshared);
            返回值:成功0,失败,返回错误编号
    
        使用pthread_mutexattr_getrobust函数获取健壮的互斥量的值。
        #include <pthread.h>
        int pthread_mutexattr_getrobust(const pthread_mutexattr_t *restrict attr,int *restrict robust);
        int pthread_mutexattr_setrobust(pthread_mutexattr_t *attr,int robust);
            返回值:成功0,失败,返回错误编号
        默认值是PTHREAD_MUTEX_STALLED,这意味着持有互斥量的进程终止时不需要采取特别的动作。
        另外一个值时PTHREAD_MUTEX_ROBUST,这个值将导致线程调用pthread_mutex_lock获取锁,而该锁被另一个进程持有,但它终止时并没有
        对该锁进行解锁,此是进程会阻塞,从pthread_mutex_lock返回的值为ROWNERDEAD而不是0.
    
        调用pthread_mutex_consistent函数,指明与该互斥量相关的状态互斥量解锁之前时一致的。
        #include <pthread.h>
        int pthread_mutex_consistent(pthread_mutex_t *mutex);
            返回值:成功0,失败,返回错误编号
    
        #include <pthread.h>
        int pthread_mutexattr_gettype(const pthread_mutexattr_t *restrict attr,int *restrict type);
        int pthread_mutexattr_settype(pthread_mutexattr_t *attr,int type);
            返回值:成功0,失败,返回错误编号
    
    
    读写锁属性:
        #include <pthread.h>
        int pthread_rwlockattr_init(pthread_rwlockattr_t *attr);
        int pthread_rwlockattr_destroy(pthread_rwlockattr_t *attr);
            返回值:成功0,失败,返回错误编号
        #include <pthread.h>
        int pthread_rwlockattr_getpshared(const pthread_rwlockattr_t *restrict attr,int *restrict pshared);
        int pthread_rwlockattr_setpshared(pthread_rwlockatrr_t *attr,int pshared);
            返回值:成功0,失败,返回错误编号
    
    条件变量属性:
        进程共享属性和时钟属性。
        #include <pthread.h>
        int pthread_condattr_init(pthread_condattr_t *attr);
        int pthread_condattr_destroy(pthread_conattr_t *attr);
            返回值:成功0,失败,返回错误编号
        #include <pthread.h>
        int pthread_condattr_getpshared(const pthread_condattr_t *restrict attr,int *restrict pshared);
        int pthread_condattr_setpshared(pthread_condattr_t *attr,int pshared);
            返回值:成功0,失败,返回错误编号
        #include <pthread.h>
        int pthread_condattr_getclock(const pthread_condattr_t *restrict attr,clockid_t *restrict clock_id);;
        int pthread_condattr_setclock(pthread_condattr_t *attr,clokid_t clock_id);
            返回值:成功0,失败,返回错误编号
    
    屏障属性:
        #include <pthread.h>
        int pthread_barrierattr_init(pthread_barrierattr_t *attr);
        int pthread_barrierattr_destroy(pthread_barrierattr_t *attr);
            返回值:成功0,失败,返回错误编号
        #include <pthread.h>
        int pthread_barrierattr_getpshared(const pthread_barrierattr_t *restrict attr,int *restrict pshared);
        int pthread_barrierattr_setpshared(pthread_barrierattr_t *attr,int pshared);
            返回值:成功0,失败,返回错误编号
        进程共享属性的值可以是PTHREAD_PROCESS_SHARED(多进程中的多个线程可用),
        可以时PTHREAD_PROCESS_PRIVETE(只有初始化屏障的那个进程内的多个线程可用)。
    
    
    重入:
        POSIX.1还提供了以线程安全的方式管理FILE对象的方法。可以使用glockfile和ftrylockfile获取给定FILE对象关联的锁。
        这个锁时递归的:当你占有这把锁的时候,还是可以再次获取该锁,而且不会导致死锁。虽然这种锁的具体实现并不规定,但要求所有
        操作FILE对象的标准I/O例程的动作行为必须看起来就像它们内部调用了flockfile和funlockfile。
        #include <stdio.h>
        int ftrylockfile(FILE *fp);
            返回值:成功,0,失败,返回非0数值
        void flockfile(FILE *fp);
        void funlockfile(FILE *fp);
    
        如果标准的I/O例程都获取它们各自的锁,那么在做一次一个字符的I/O时就会出现严重的性能下降。在这种情况下,需要对每一个字符
        的读写操作进行获取锁和释放锁的动作。为了避免这种开销,出现了不加锁版本的基于字符的标准I/O例程。
        #include <stdio.h>
        int getchar_unlocked(void);
        int getc_unlocked(FILE *fp);
            返回值:成功,返回下一个字符,若遇到文加尾或者出错,返回EOF
        int putchar_unlocked(int c);
        int putchar_unlocked(int c,FILE *fp);
            返回值:成功,返回c,失败,返回EOF
    
    
    线程特定数据:
        1、有时候需要维护基于每个线程的数据。因为线程ID并不能保证是小而连续的整数,所以就不能简单地分配一个每线程数据数组,用线程
        ID作为数组的索引。即使线程ID确实是小而连续的整数,我们还希望有一些额外的保护,防止某个线程的数据与其他线程的数据混淆。
        2、它提供了基于进程的接口适应多线程环境的机制。一个很明显的实例就是errno。以前的接口(线程出现以前)把errno定义为进程
        上下文中全局可访问的整数。系统调用和库例程在调用或执行失败是设置errno,把它作为操作失败时的附属结果。为了让线程也能够
        使用那些原本基于进程的系统调用和库例程,errno被重新定义为线程私有数据。这样,一个线程做了重置errno的操作也不会影响进程
        中其他线程的errno值。
    
        在分配线程特定数据之前,需要创建与该数据关联的键。这个键将用于获取对线程特定数据的访问。使用pthread_key_create创建一个
        键:
        #include <pthread.h>
        int pthread_key_create(pthread_key_t *keyp,void (*destructor)(void *));
            返回值:成功0,失败,错误编号
        创建的键储存在keyp指定的内存单元内,这个键可以被进程中的所有线程使用,但每个线程把这个键与不同的线程特定数据地址进行
        关联。创建新键时,每个进程的数据地址设为空值。
        它可以为该键关联一个可选择的析构函数。当这个线程退出时,如果数据地址已经置为非空,那么析构函数就会调用,它的唯一参数
        时该数据地址。如果传入的析构函数为空,就表明没有析构函数与这个键关联。当线程调用pthread_exit或者线程执行返回。正常
        退出,析构函数就会被调用。如果线程调用了exit、_exit、_Exit或者abort,或者出现其他非正常的退出时,就不会调用析构函数。
    
        我们可以通过调用pthread_key_delete来取消键与线程特定数据值之间的关联关系。
        #include <pthread.h>
        int pthread_key_delete(pthread_key_t key);
            返回值:成功0,失败,错误编号。
    
    
        解决系统的线程之间的竞争调用pthread_once:
        #include <pthread.h>
        pthread_once_t initflag=PTHREAD_ONCE_INIT;
        int pthread_once(pthread_once_t *initflag,void (*initfn)(void));
            返回值:成功0,失败,错误编号
        initflag必须是一个非本地变量,而且初始化必须时PTHREAD_ONCE_INIT。
    
    
        键一旦创建成功,就可以通过pthread_setspecific函数把键和线程特定数据关联起来,
        可以通过pthread_getspecific函数获得线程特定数据的地址:
        #include <pthread.h>
        void *pthread_getspecific(pthread_key_t key);
            返回值:线程特定数据值,若没有值与该键关联,返回NULL
        int pthread_setspecific(pthread_key_t key,const void *value);
            返回值:成功0,失败,错误编号
    
    取消选项:
        有两个线程属性并没有包含在pthread_attr_t结构中,它们是可取消状态和可取消类型:
        着两个属性影响这线程在响应pthread_cancle函数时所呈现的行为。
        可取消状态属性可以是PTHREAD_CANCEL_ENABLE,也可以是PTHREAD_CANCEL_DISABLE。
        线程可以通过调用pthread_setcancelstate修改它的可取消状态:
        #include <pthread.h>
        int pthread_setcancelstate(int state,int *oldstate);
            返回值:成功,0,失败,返回错误编号。
        ptread_setcancelstate把当前的可取消状态设置state,把原来的可取消请求存储在由oldstate
        指向的内存单元。
    
        调用pthread_testcancel函数在程序中添加自己的取消点:
        #include <pthread.h>
        void pthread_testcancel(void);
    
        我们所描述的默认的取消类型也称为i推迟取消。调用pthread_cancel后,在线程到达取消点之前,并不会
        真正的取消。可以通过pthread_setcanceltype来修改取消类型。
        #include <pthread.h>
        int pthread_setcanceltype(int type,int *oldtype);
            返回值:成功0,失败,返回错误编号。
        pthread_setcanceltype函数把取消类型设置为type(类型参数可以是PTHREADCANCEL_DEFERRED,//拖延,延缓,推迟
        也可以是PTHREAD_CANCEL_ASYNCHRONOUS//异步的),把原来的取消类型返回到oldtype指向的整型单元。
        异步取消和推迟取消不同,因为使用异步取消时,线程可以在任意时间撤销,不是非得遇到取消点才能被取消。
    
    线程和信号:
        进程使用sigprocmask函数来阻止信号发送。然而,sigprocmask的行为在多线程的进程中并没有定义,线程必须使用:
        #include <signal.h>
        int pthread_sigmask(int how,const sigset_t *restrict set,sigset_t *restrict oset);
            成功0,失败,错误编号
        pthread_sigmask工作在线程中,而且失败时返回错误码,不再像sigprocmask中那样设置errno并返回-1.
    
        set参数包含线程用于信号屏蔽字的信号集
    
        how参数可以取下列值之一:SIG_BLOCK,把信号集添加到线程信号屏蔽字中
        SIG_SETMASK,用信号集替换线程的信号屏蔽字
        SIG_UNBLOCK,从线程信号屏蔽字中移除信号集
    
        oset参数不为空,线程之前的信号屏蔽字就存储在它指向的sigset_t结构中
    
        线程可以通过把set参数设置为NULL,并把oset参数设置为sigset_t结构的地址,来获取当前的信号屏蔽字。这种情况下how就会
        被忽略。
        线程可以通过调用sigwait等待一个或者多个信号的信号:
        #include <signal.h>
        int sigwait(const sigset_t *restrict set,int *restrict signop);
            返回值:成功0,失败,错误编号
        set参数指定了线程等待的信号集。返回时,signop指向的整数将包含发送信号的数量。
    
        要把信号发送给进程,可以调用kill。要把信号发送给线程,可以调用pthread_kill:
        #include <signal.h>
        int pthread_kill(pthread_t thread,int signo);
            返回值:成功0,失败,返回错误编号
        可以传一个0值的signo来检查线程是否存在。如果信号的默认处理动作时终止该进程,那么把信号传递给某个线程仍然会杀死
        整个进程。
    
        注意,闹钟定时器时进程资源,并且所由的线程共享相同的闹钟。所以,进程的多个线程不可能不干扰地使用闹钟定时器。
    
        程序,我们等待信号处理程序设置标志表明主程序应该退出。唯一可运行的控制线程就是主线程和信号处理程序,所以阻塞信号足以
        避免错失标志修改。在线程中,我们需要使用互斥量来保护标志。
            #include <stdlib.h>
            #include <stdio.h>
            #include <unistd.h>
            #include <pthread.h>
            #include <signal.h>
    
            int quitflag;
            sigset_t mask;
    
            pthread_mutex_t lock=PTHREAD_MUTEX_INITIALIZER;
            pthread_cond_t waitloc=PTHREAD_COND_INITIALIZER;
    
            void *thr_fn(void *arg)
            {
                int err,signo;
    
                for(;;){
                    err=sigwait(&mask,&signo);
                    if(err!=0)
                            printf("sigwait failed:%d",err);
                    switch(signo){
                       case SIGINT:
                            printf("
    interrupt
    ");
                            break;
                    case SIGQUIT:
                            pthread_mutex_lock(&lock);
                            quitflag=1;
                            pthread_mutex_unlock(&lock);
                            pthread_cond_signal(&waitloc);
                            return 0;
                    default:
                            printf("unexpected signal %d
    ",signo);
                            exit(1);
                    }
                }
            }
    
    
            int main(void)
            {
                int err;
                sigset_t oldmask;
                pthread_t tid;
    
                sigemptyset(&mask);
                sigaddset(&mask,SIGINT);
                sigaddset(&mask,SIGQUIT);
                if((err=pthread_sigmask(SIG_BLOCK,&mask,&oldmask))!=0)
                    printf("SIG_BLOCK error:%d
    ",err);
    
                err=pthread_create(&tid,NULL,thr_fn,0);
                if(err!=0)
                    printf("can't create thread:%d
    ",err);
    
                pthread_mutex_lock(&lock);
                while(quitflag==0)
                {
                    pthread_cond_wait(&waitloc,&lock);
                }
                pthread_mutex_unlock(&lock);
    
                quitflag=0;
    
                if(sigprocmask(SIG_SETMASK,&oldmask,NULL)<0)
                    printf("SIG_SETMASK error
    ");
                exit(0);
            }
        执行:
        $ ./example5
        ^C
        interrupt
        ^C
        interrupt
        ^C
        interrupt
        ^C
        interrupt
        ^C
        interrupt
        ^C
        interrupt
        ^
    
    
    线程和fork:
        清理锁状态,可以通过pthread_atfork函数建立fork处理程序。
        #include <pthread.h>
        int pthread_atfork(void (*prepare)(void),void (*parent)(void),void (*child)(void));
            返回值:成功0,失败,错误编号。
    
    线程和I/O:
        pread和pwrite函数。这些函数在多线程环境下是非常有用的,因为进程中的所有线程共享相同的文件描述符。
    
        考虑两个线程,在同一时间对同一个文件描述符进行读写操作。
            线程A             线程B 
        lseek(fd,300,SEEK_SET);         lseek(fd,700,SEEK_SET);
        read(fd,buf1,100);          read(fd,buf2,100);
        如果线程A执行lseek然后线程B在线程A调用read之前调用lseek,那么两个线程最终会读取同一条记录。很显然这不是我们
        希望的。
        为了解决这个问题,可以使用pread,使偏移量的设定和数据的读取称为一个原字操作。
            线程A             线程B
        pread(fd,buf1,100.300);         pread(fd,buf2,100.700);
        使用pread可以确保线程A读取偏移量为300的记录,而线程B读取偏移量为700的记录。
        可以使用pwrite来解决并发线程对同一文件进行写操作的问题。
    
    技术不分国界
  • 相关阅读:
    发款php蜘蛛统计插件只要有mysql就可用
    利用mysql来做一个功能强大的在线计算器
    IndexOf、lastIndexOf、substring的用法
    需求分析工具的软件
    JSP程序设计
    灵活有效的数据仓库解决方案:第1部分:客户互动和项目计划
    灵活有效的数据仓库解决方案,第3部分:设计并实现仓库ETL过程
    Devpress.XtraGrid.GridControl.GridView 属性
    8种人将被淘汰
    WCF配置中遇到的问题:如何把Hostname修改成IP
  • 原文地址:https://www.cnblogs.com/angels-yaoyao/p/12443612.html
Copyright © 2020-2023  润新知