• Apue.2e Chapter1112 Thread


    这两章讲诉线程和线程控制的相关api。

    为什么要有线程呢,因为线程更快,并发操作操作资源更简单。

    注:线程系函数多半并不设置errno,而是直接返回错误码。

    线程独立的资源:线程ID,一组寄存器、栈、调度优先级、策略、信号屏蔽字、errno和private data;

    线程ID:pthread_t,实现可以用一个结构来表示,因此不能直接操作,需要用

    #include <pthread.h>

    int pthread_equal(pthread_t tid1,pthread_t tid2);

    来比较是否相等(非0)。

    线程可以通过

    pthread_t pthread_self(void);获得自身的tid;

    创建线程:

    int pthread_create(pthread_t *restrict tidp, const pthread_t attr_t *restrict attr,

                void *(*start_rtn)(void *),void *restrict arg);

    attr是所创建线程的属性,可通过

    int pthread_attr_init(pthread_attr_t attr);来初始化,然后通过

    int pthread_attr_get/setdetachstate来获取/设置以分离状态启动线程。[分离状态:不再等待线程的终结状态,操作系统会自动回收资源(类似于孤儿进程)]

    通过int pthread_attr_get/setstack来获取/设置栈属性,包括栈大小和栈的最低地址(不一定是起始地址,取决于增长方向),如果仅修改大小,使用pthread_attr_get/setstacksize;

    使用int pthread_attr_get/setguardsize来获取/设置警戒缓冲区大小(避免栈溢出的扩展内存大小)

    以上都是通过改变pthread_attr_t结构来实现的,还有几个属性是通过函数直接设置的,包括:

    可取消选项:int pthread_setcancelstate(int state,int *oldstate),注意即使设置为PTHREAD_CANCEL_ENABLE,线程也不一定会立刻取消,必须通过pthread_setcanceltype(int type, int *oldtype)来设置其取消类型为PTHREAD_CANCEL_AYSNCHRONOUS(异步取消),才会立刻取消;否者默认为延迟取消,必须在合适的取消点才会取消线程函数,可以通过pthread_testcancel来自定义取消点。

    并发度:int pthread_get/setconcurrency,如果操作系统控制并发度,get返回0;set参数为0取消设置,其他值用来设置自己希望的并发度。

    注意:线程创建api,书中Linux2.4是使用clone system call实现的,这个实现方式被称为Linux Threads,已经过时,在2.6+ kernel中,实现方式改为NPTL,该实现是符合Posix.1兼容性需求的。

    线程终止:

    除了正常返回外,可以通过

    void pthread_exit(void *rval_ptr);

    退出线程函数。参数可以被

    int pthread_join(pthread_t thread, void **rval_ptr);

    获得。后者会阻塞线程直到等待的线程退出(rval指向返回数据)或被取消(*rval==PTHREAD_CANCELED),其行为类似waitpid。

    如果线程已经被置于分离状态,调用失败,返回EINVAL。

    取消线程可以通过

    int pthread_cancel(pthread_t tid);来完成,效果同调用了参数为PTHREAD_CANCELED的pthread_exit函数。

    可以注册线程清理程序(类似进程的atexit),对应函数为

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

    void pthread_cleanup_pop(int execute);

    以非0参数调用后者,可以主动触发前者注册的函数;线程非正常结束时,会自动弹出(类似栈行为)注册的函数,并以参数调用。

    由于两者可以被实现为宏,所以最好配对使用(在同一作用域)。

    线程同步:

    Unix线程同步的工具与Windows大体一致(比win32少一些,也不区分用户模式和内核模式),主要包括互斥量(mutex),读写锁(RW lock)和条件变量,至于信号量、消息队列和管道等属于进程间通信方式,将在chapter15学习。

    可以通过

    int phread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);

    int phread_mutex_destroy(pthread_mutex_t *restrict mutex);

    来创建和销毁互斥量。

    attr是互斥量的属性,主要包括进程共享(int pthread_mutexattr_get/setshared)和类型(int pthread_mutexattr_get/settype)。前者可以将互斥量设置为进程间共享,从而使互斥量用于进程间通信;后者可将互斥量设置为可递归的|包含错误检查的|正常的这三种模式,可递归允许多次获得锁,对应着多次的unlock,在某些时候比较有用。

    可以使用

    int pthread_mutex_lock(pthread_mutex_t *mutex);

    int pthread_mutex_trylock(pthread_mutex_t *mutex);

    int pthread_mutex_unlock(pthread_mutex_t *mutex);

    来加锁/尝试加锁/解锁;trylock如果失败,返回EBUSY

    互斥量经常和引用计数技术同时使用。

    如果互斥量是非递归的,已经获得锁的情况下尝试再次获得锁,会导致死锁。

    将锁嵌入结构,是多线程环境下常用的一种解决方案。

    读写锁:

    即共享/独占锁。

    使用

    int pthread_rwlock_init(phread_rwlock_t *restrict rwlock, const phread_rwlockattr_t *attr);

    int pthread_rwlock_destroy(phread_rwlock_t *restrict rwlock);

    来初始化/销毁一个读写锁。

    使用

    int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);

    int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);

    int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

    来加读/写锁或解锁。

    也有对应的tryrdlock/trywrlock版本,返回值类似mutex;

    属性:只支持进程共享属性,函数也是类似的get/setshared格式。

    条件变量:

    条件变量与互斥量一起使用时,允许多线程以无竞争状态等待某一条件的发生。

    int pthread_cond_init(pthread_cond_t *restrict cond, pthread_condattr_t *restrict attr);

    int pthread_cond_destroy(pthread_cond_t *cond);

    来初始化/销毁条件变量。

    int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);

    int pthread_cond_timewait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex,

                    const struct timespec *restrict timeout);

    来等待/超时等待条件变量的发生。

    调用者将锁住的条件变量传递给wait函数,后者将其放在等待条件的线程列表中,并对其解锁(原子操作),返回后,会自动再次加锁。

    超市等待的时间是绝对值(即:明确的几点几分),这点要注意。

    通知条件准备完成,可以用:

    int pthread_cond_signal(pthread_cond_t *comd);

    int pthread_cond_broadcast(pthread_cond_t *comd);

    内核会以不确定的方式发送给各个等待条件的线程(如果条件的属性被设置为进程共享,那么也会通知其他进程的等待线程)。

    重入:

    多个线程可以同时安全地调用的函数,就是所谓的线程安全函数。

    非线程安全的函数,系统可能有些线程安全版本(_r)。

    线程安全和信号处理函数可重入不完全一致,后者的要求更严格一些(异步信号安全)。

    标准流FILE结构,可以使用对应的操作函数来保证线程安全,注意由于字符处理函数的多线程版效率太低,因此额外提供了解锁版。

    线程私有数据:

    即线程本地化数据,通过创建"键"来关联线程私有数据:

    int pthread_key_create(pthread_key_t *keyp, void (*destructor)(void *));

    第二个参数是析构函数,其参数是需要析构的数据的地址(系统自动添加);

    删除使用:

    int pthread_key_delete(pthread_key_t *keyp);

    注意调用此函数不会自动激活析构。

    在多个线程中共需的数据,可以使用

    pthread_once_t initflag=PTHREAD_ONCE_INIT;

    int pthread_once(pthread_once_t *initflag, void (*initfnt)(void));

    来初始化该数据,initflag必须是非local变量;initfnt是初始化的函数;

    使用key时常用的上pthread_once函数。

    检查或关联私有数据需要使用函数:

    void *pthread_getspecific(pthread_key_t key);

    int pthread_setspecific(pthread_key_t key, const void *value);

    线程和信号:

    在线程中处理信号,不能使用sigprocmask,而要用:

    #include <signal.h>

    int pthread_sigmask(int how, const sigset_t *restrict set, sigset_t *restrict oset);

    来设置屏蔽字,然后使用

    int sigwait(const signet_t *restrict set, int *restrict signop);

    来等待信号(signop作为返回值,表明发送信号数量)。为避免错误,记得在调用sigwait之前阻塞需要等待的信号。

    常用的处理方案:在某个线程中专门等待某个信号,在其他线程中屏蔽该信号,这样就有了一个异步的专用的信号处理线程;

    如果在主线程中使用sigaction/signal函数注册了信号处理函数,在某线程中又调用了sigwait等待该信号,内核以不确定的方式将信号发往其中某一个函数。

    线程和fork:

    fork后,只存在调用fork的线程的副本,其他线程的相关锁必须手动清除。但是如果fork后立刻调用exec系,就不必管它。

    处理函数注册:

    int pthread_atfork( void (*prepare)(void),void (*parent)(void),void (*child)(void));

    prepare在fork前调用,目的是获得父进程的所有锁;

    parent在父进程fork返回前调用,目的是解锁prepare中的所有锁;

    child在子进程fork返回前调用,目的是解锁子进程副本中prepare的所有锁。

    线程和I/O:

    记得pread和pwrite即可。保证偏移和写入的原子性。

  • 相关阅读:
    centos yum 安装nginx 后增加模块
    mysql 设置不了短串密码怎么办 You must reset your password using ALTER USER statement before executing this statement.
    Centos7 安装mysql后登录提示:Access denied for user 'root'@'localhost'
    mysql安装历程
    yum.rpm,repo源
    MySql-Server和MySql-Client的关系
    阿里云JKD镜像
    关于字符编码
    Dockerfile编写制作centos7.7+php5.6+nginx镜像
    PHP中常见日期、月份、时间戳列表(strtotime、date、mktime的用法)
  • 原文地址:https://www.cnblogs.com/livewithnorest/p/2908897.html
Copyright © 2020-2023  润新知