UNIX环境高级编程(第11章 线程)

1线程与进程

    一个UNIX进程可以理解为一个线程加上地址空间、文件描述符和其他数据。线程和进程的区别在于多个线程可以共享一个地址空间,而做不同的事情。


2线程标识

头文件

#include <pthread.h>

函数原型

int pthread_equal(pthread_t tid1, pthread_t tid2);

参数

tid1:线程标识1

tid2:线程标识2

返回

若相等则返回非0值,否则返回0

功能

对两个线程ID进行比较


头文件

include <pthread.h>

函数原型

pthread_t pthread_self(void);

参数

void

返回

返回调用线程的线程ID

功能

获得自身线程ID


3线程创建

头文件

include <pthread.h>

函数原型

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

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

参数

tidp:获取新创建线程的线程ID

attr:线程属性

start_rtn:新创建的线程从start_rtn函数的地址开始运行

arg:start_rtn的指针参数

返回

若成功则返回0,否则返回错误编号

功能

创建线程

    线程创建时并不能保证哪个线程会先运行:是新创建的线程还是调用线程。新创建的线程可以访问进程的地址空间,并且继承调用线程的浮点环境和信号屏蔽字,但是该线程的未决信号集被清除。


4线程终止

    单个线程可以通过下列三种方式退出,在不终止整个进程的情况下停止它的控制流:

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

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

(3)线程调用pthread_exit

头文件

include <pthread.h>

函数原型

void pthread_exit(void *rval_ptr);

参数

rval_ptr:保存线程退出后返回的值

返回

void

功能

线程终止

        rval_ptr是无类型指针,进程中的其他线程可以通过调用pthread_join函数访问该指针。

头文件

include <pthread.h>

函数原型

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

参数

thread:指定的线程ID

rval_ptr:获取线程退出时的返回值

返回

若成功返回0,否则返回错误编码

功能

等待一个线程的结束

    调用pthread_join后,调用线程将一直阻塞,直到指定的线程调用pthread_exit、从启动例程中返回或者被取消。

可以通过调用pthread_join自动把线程置于分离状态,这样资源就可以恢复。如果线程已经处于分离状态,pthread_join调用就会失败,返回EINVAL

头文件

include <pthread.h>

函数原型

int pthread_cancel(pthread_t tid);

参数

tid:线程ID

返回

若成功返回0,否则返回错误编码

功能

请求取消同一进程中的其他线程。注意:pthread_cancel并不等待线程终止

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

头文件

include <pthread.h>

函数原型

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

void pthread_cleanup_pop(int execute);

参数

rtn:线程清理函数

arg:参数

execute:表示执行到pthread_cleanup_pop时是否在弹出清理函数的同时执行该函数,若execute0,清理函数将不被执行;若为非0,则执行。

返回

void

功能

线程注册清理函数。Pthread_cleanup_pushpthread_cleanup_pop可以实现为宏,所以在使用时需要配对出现。


头文件

include <pthread.h>

函数原型

int pthread_detach(pthread_t tid);

参数

tid:线程ID

返回

若成功返回0,否则返回错误编号

功能

使线程进入分离状态

    在默认情况下,线程的终止状态会保存到对该线程调用pthread_join,如果线程已经处于分离状态,线程的底层存储资源可以在线程终止时立即被收回。当线程被分离时,并不能用pthread_join函数等待它的终止状态。对分离状态的线程进行pthread_join的调用会产生失败,返回EINVAL.

    进程原语与线程原语的比较:

进程原语

线程原语

描述

fork

exit

waitpid

atexit

getpid

abort

pthread_create

pthread_exit

pthread_join

pthread_cleanup_push/pop

pthread_self

pthread_cancel

创建新的控制流

从现有的控制流中退出

从控制流中得到退出状态

注册在退出控制流时调用的函数

获取控制流的ID

请求控制流的非正常退出

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <pthread.h>

void printids(const char *s)
{
    pid_t pid;
    pthread_t tid;

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

    printf("%s pid %u tid %u (0x%x)\n", s, (unsigned int)pid, (unsigned int)tid, (unsigned int)tid);

    return ;
}


void *thr_fn1(void *arg)
{
    printids("thread 1:");

    return ((void *)1);      /*线程从启动例程中返回*/
}

void *thr_fn2(void *arg)
{
    printids("thread 2:");

    pthread_exit((void *)2); /*线程调用pthread_exit*/
}

int main(void)
{
    int err;
    pthread_t tid1, tid2;
    void *tret;

    /*创建线程1*/
    err = pthread_create(&tid1, NULL, thr_fn1, (void *)1);
    if (err != 0)
    {
        printf("can't create thread 1: %s\n", strerror(err));
        return -1;
    }

    /*创建线程2*/
    err = pthread_create(&tid2, NULL, thr_fn2, NULL);
    if (err != 0)
    {
        printf("can't create thread 2: %s\n", strerror(err));
        return -1;
    }

    /*调用线程阻塞,直到线程1退出*/
    err = pthread_join(tid1, &tret);
    if (err != 0)
    {
        printf("can't join with thread 1: %s\n", strerror(err));
        return -1;
    }

    printf("thread 1 exit code %d\n", (int)tret);

    /*调用线程阻塞,直到线程2退出*/
    err = pthread_join(tid2, &tret);
    if (err != 0)
    {
        printf("can't join with thread 1: %s\n", strerror(err));
        return -1;
    }

    printf("thread 2 exit code %d\n", (int)tret);

    printids("main thread: ");

    exit(0);
}
[root]# gcc thread.c -pthread
[root]# ./a.out 
thread 1: pid 2628 tid 1113676096 (0x42615940)
thread 2: pid 2628 tid 1124165952 (0x43016940)
thread 1 exit code 1
thread 2 exit code 2
main thread:  pid 2628 tid 3926034592 (0xea0288a0)
[root]# 

5线程同步

5.1互斥量

互斥量从本质上来说是一把锁,在访问共享资源前对互斥量进行加锁,在访问完成后释放互斥量上的锁。

互斥变量的初始化:

#include <pthread.h>

int pthread_mutex_init(pthread_mutex_t *restrict mutex,

const pthread_mutexattr_t *restrict attr);

int pthread_mutex_destroy(pthread_mutex_t *mutex);

返回值:若成功返回0,否则返回错误编号

   互斥变量的加解锁:

#include <pthread.h>

int pthread_mutex_lock(pthread_mutex_t *mutex);//加锁,如果互斥量已上锁,调用线程将阻塞直到互斥量被解锁。

int pthread_mutex_unlock(pthread_mutex_t *mutex);//解锁

int pthread_mutex_trylock(pthread_mutex_t *mutex);//尝试加锁,如果互斥量已上锁,调用线程返回失败返回EBUSY而不会阻塞

返回值:若成功返回0,否则返回错误编号

死锁的概念:

各并发进程互相等待对方所拥有的资源,且这些并发进程在得到对方的资源之前不会释放自己所拥有的资源。从而造成大家都想得到资源而又都得不到资源,各并发进程不能继续向前推进的状态。

死锁的分类:

1】自死锁

        如果一个执行线程试图去获得一个自己已经持有的锁,它将不得不等待锁被释放,但因为它正在忙着等待这个锁,所以自己永远也不会有机会是否锁,最终结果就是死锁。

2ABBA死锁

线程1持有A锁,并试图获得B锁;线程2持有B锁,并试图获得A锁。两个线程都

在相互请求另一个线程拥有的资源,所以这两个线程都无法向前运行,于是就产生死锁。

避免死锁:

1)按顺序加锁。

2)防止发生饥饿。

3)不要重复请求同一个锁。

4)设计应力求简单

/*使用两个互斥量*/
#include <stdlib.h>
#include <pthread.h>

#define NHASH 29
#define HASH(fp) (((unsigned long)fp) % NHASH)

struct foo *fh[NHASH];                               /*全局列表*/
pthread_mutex_t hashlock = PTHREAD_MUTEX_INITIALIZER;/*全局互斥量,用于保护全局列表fh*/


struct foo
{
    int             f_count;
    pthread_mutex_t f_lock;  /*结构内互斥量,用于保护结构内的f_count变量*/
    struct foo     *f_next;  /*该变量由全局互斥量hashlock保护*/
    int             f_id;
    /*... more stuff here ...*/
};

/*分配foo对象*/
struct foo *foo_alloc(void)
{
    struct foo *fp;
    int        idx;
    
    if ((fp = malloc(sizeof(struct foo))) != NULL)
    {
        fp->f_count = 1;
        if (pthread_mutex_init(&fp->f_lock, NULL) != 0)
        {
            free(fp);
            return NULL;
        }
        idx = HASH(fp);                    /*将fp的地址值转为无符号长整型再模NHASH*/
        pthread_mutex_lock(&hashlock);
        fp->f_next = fh[idx];              /*首次使用时,fh[idx]的值为NULL。将已经存在的fh[idx]链表链接在新节点fp之后*/
        fh[idx] = fp;/*=fp非fp->f_next*/   /*新生成的fp作为fh[idx]的首节点。所有哈希值为idx的fp都挂在fh[idx]的链表上*/
        pthread_mutex_lock(&fp->f_lock);
        pthread_mutex_unlock(&hashlock);
        /*   ... continue initialization ...   */
        pthread_mutex_unlock(&fp->f_lock);
        
    }

    return fp;
}

/*增加foo对象的引用计数*/
void foo_hold(struct foo *fp)
{
    pthread_mutex_lock(&fp->f_lock);
    fp->f_count++;
    pthread_mutex_unlock(&fp->f_lock);
}

/*查找已经存在的foo对象*/
struct foo *foo_find(int id)
{
    struct foo *fp;
    int        idx;

    idx = HASH(fp);
    /*foo_find函数内的加锁顺序: 先hashlock锁,再fp->f_lock锁*/
    pthread_mutex_lock(&hashlock);
    for (fp = fh[idx]; fp != NULL; fp = fp->f_next)
    {
        if (fp->f_id == id)
        {
            foo_hold(fp);
            break;
        }
    }
    pthread_mutex_unlock(&hashlock);

    return fp;
}

/*释放foo对象的引用*/
void foo_rele(struct foo *fp)
{
    struct foo *tfp;
    int         idx;
    
    pthread_mutex_lock(&fp->f_lock);
    if (fp->f_count == 1)/*最后一个引用*/
    {
        /*fp对象在全局散列列表中,要从中删除fp对象时,需要用到hashlock锁*/
        /*
            为满足加锁顺序: 先hashlock后fp->f_lock,需要先将f_lock解锁。如果此处不
            先解锁fp->f_lock而是直接加锁hashlock,则其他线程可能在调用foo_find已经
            持有hashlock锁而在等待fp->f_lock锁。此时本线程持有fp->f_lock锁且尝试持有
            hashlock锁,则可能产生死锁。故而,当有多把锁时,需要保证锁顺序一致。
        */
        pthread_mutex_unlock(&fp->f_lock);
        pthread_mutex_lock(&hashlock);

        /*此处加锁fp->f_lock可能要等待别的线程调用foo_hold加锁增加引用计数*/
        pthread_mutex_lock(&fp->f_lock);
        /*重新持有fp->f_lock锁时f_count可能被别的线程增1,故而需重新检查条件,判断是否需要释放结构*/
        if (fp->conut != 1)
        {
            fp->f_count--;
            pthread_mutex_unlock(&fp->f_lock);
            pthread_mutex_unlock(&hashlock);
            return ;
        }
        /*fh[idx]的链表中移除fp*/
        idx = HASH(fp);
        tfp = fh[idx];
        if (tfp == fp)
        {
            /*fp刚好是fh[idx]的首节点*/
            fh[idx] = fp->f_next;
        }
        else
        {
            while (tfp->f_next != fp)
            {
                tfp = tfp->f_next;
            }
            tfp->f_next = fp->f_next;
        }
        /*释放锁的顺序和死锁无关。不过建议以获得锁的相反顺序来释放锁*/
        pthread_mutex_unlock(&hashlock);/*本例没遵循上述建议来释放锁,不过也没问题*/
        pthread_mutex_unlock(&fp->f_lock);
        pthread_mutex_destroy(&fp->f_lock);
        free(fp);
    }
    else
    {
        fp->f_count--;
        pthread_mutex_unlock(&fp->f_lock);
    }
}

   上例中用了两个互斥量,为避免死锁,遵循了按顺序加锁的规则。但如此加、解锁太过复杂。故而需要重新审视原来的设计,力求简单。如可以使用散列列表锁hashlock来保护数据结构foo中的引用计数f_count,结构互斥量f_lock用于保护foo结构中的其他成员,如此便可使事情大大简化。


5.2读写锁

读写锁与互斥量类似,不过读写锁允许更高的并行性。

读写锁也叫共享-独占锁,当读写锁以读模式锁住时,它是以共享模式锁住的;当它以写模式锁住时,它是以独占模式锁住的。

读写锁有三种状态:读模式下加锁状态,写模式下加锁状态,不加锁状态。

当读写锁是写加锁状态时,在这个锁被解锁之前,所有试图对这个锁加锁的线程都被阻塞。

当读写锁是读加锁状态时,所有试图以读模式对它进行加锁的线程都可以得到访问权,但是如果线程希望以写模式对此锁进行加锁,它必须阻塞直到所有的线程释放读锁。

一次只有一个线程可以占有写模式的读写锁,但是多个线程可以同时占有读模式的读写锁。

读写锁非常适合于对数据结构读的次数远大于写的情况。


读写锁初始化:

#include <pthread.h>

int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,

const pthread_rwlockattr_t *restrict attr);

int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);

返回值:若成功返回0,否则返回错误编号

    读写锁加解锁:

#include <pthread.h>

int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);

int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);

int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

返回值:若成功返回0,否则返回错误编号

/*线程同步之使用读写锁*/

#include <stdlib.h>
#include <pthread.h>

struct job
{
    struct job *j_next;
    struct job *j_prev;
    pthread_t   j_id;
    /* ... more stuff here ...   */
};

struct queue
{
    struct job      *q_head;
    struct job      *q_tail;
    pthread_rwlock_t q_lock;
};


/*Initialize a queue*/
int queue_init(struct queue *qp)
{
    int err;
    
    qp->q_head = NULL;
    qp->q_tail = NULL;

    err = pthread_rwlock_init(&qp->q_lock, NULL);
    if (err != 0)
    {
        return err;
    }

    /*   continue initialization ... */

    return 0;
}

/*Insert a job at the head of the queue*/
void job_insert(struct queue *qp, struct job *jp)
{
    pthread_rwlock_wrlock(&qp->q_lock);

    jp->j_next = qp->q_head;
    jp->j_prev = NULL;

    if (qp->q_head !=  NULL)
    {
        qp->q_head->j_prev = jp;
    }
    else
    {
        qp->q_tail = jp;
    }
    qp->q_head = jp;

    pthread_rwlock_unlock(&qp->q_lock);
}

/*   Append a job on the tail of the queue*/
void job_append(struct queue *qp, struct job *jp)
{
    pthread_rwlock_wrlock(&qp->q_lock);

    jp->j_next = NULL;
    jp->j_prev = qp->q_tail;
    if (qp->q_tail != NULL)
    {
        qp->q_tail->j_next = jp;
    }
    else
    {
        qp->q_head = jp;
    }
    
    qp->q_tail = jp;
    
    pthread_rwlock_unlock(&qp->q_lock);
}


/* Remove the given job from a queue */
void job_remove(struct queue *qp, struct job *jp)
{
    pthread_rwlock_wrlock(&qp->q_lock);
    
    if (jp == qp->q_head)
    {
        qp->q_head = jp->j_next;
        if (qp->q_tail == jp)
            qp->q_tail = NULL;
    }
    else if (jp == qp->q_tail)
    {
        qp->q_tail = jp->j_prev;
        if (qp->q_head == jp)
            qp->q_head = NULL;
    }
    else
    {
        jp->j_prev->j_next = jp->j_next;
        jp->j_next->j_prev = jp->j_prev;
    }
    
    pthread_rwlock_unlock(&qp->q_lock);
}


/* Find a job for the given thread ID*/
struct job * job_find(struct queue *qp, pthread_t id)
{
    struct job *jp;

    if (pthread_rwlock_rdlock(&qp->q_lock) != 0)
    {
        return NULL;
    }

    for (jp = qp->q_head; jp != NULL; jp = jp->j_next)
    {
        if (pthread_equal(jp->j_id, id))
            break;
    }

    pthread_rwlock_unlock(&qp->q_lock);

    return jp;
}



5.3条件变量

条件变量给多个线程提供了一个会合的场所。条件变量与互斥量一起使用时,允许线程以无竞争的方式等待特定的条件发生。

条件本身是由互斥量保护的。线程在改变条件状态前必须首先锁住互斥量,其他线程在获得互斥量之前不会察觉到这种改变,因为必须锁定互斥量以后才能计算条件。

条件变量初始化:

#include <pthread.h>

int pthread_cond_init(pthread_cond_t *restrict cond,

                 pthread_condattr_t *restrict attr);

int pthread_cond_destroy(pthread_cond_t *cond);

   等待条件变为真:

#include <pthread.h>

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);

    通知线程条件已满足:

#include <pthread.h>

int pthread_cond_signal(pthread_cond_t *cond);

int pthread_cond_broadcast(pthread_cond_t *cond);




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值