apue第11章 线程

1、引言

2、线程概念

  • 即使在单核处理器上,由于某些线程阻塞时另外一些线程也可以运行,所以多线程还是可以改善响应时间和吞吐量的
  • 每个线程都有线程id,一组寄存器值,栈,调度优先级和策略,信号屏蔽字,errno变量以及线程私有数据。
  • 一个进程的所有信息对该线程都是共享的:程序的代码,程序的全局内存和堆内存, 栈以及文件描述符。

3、线程标志

  • 线程标志为线程ID,但是该id是一个结构体,只在它所属的进程上下文才有意义。
  • 判断两个线程相等的函数
#include 
int pthread_equal(pthread_t tid1, pthread_t tid2);
//返回值:若相等,返回非0数值;否则,返回0
  • 获得线程自身的id
#include 
pthread_t pthread_slef(void);
//返回值:调用线程的线程ID

4、线程创建

线程创建的原型为:

#include 
int pthread_create(pthread_t *restrict tidp,
                   const pthread_attr_t *reatrict attr,
                   void *(*start_rtn)(void *), 
                   void *restrict arg);
//返回值:若成功则返回0;否则,返回错误编号
  • 新线程的ID会被设置成tidp指向的内存单元
  • attr参数用于定制各种不同的线程属性
  • 线程从start_rtn函数开始运行,该函数参数为void*类型,返回值也为void*
  • 该函数传入参数为arg,如果有多个参数需要写成一个结构体,传入结构体指针。
  • 新创建的线程可以访问进程的地址空间,并继承调用线程的浮点环境和信号屏蔽字,但是该线程的挂起信号集可能被清除。
  • 调用失败会返回错误码,而不是errno.

例子:创建一个新线程并打印线程id

#include 
#include 
#include 
#include 

pthread_t ntid;

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

    pid = getpid();
    tid = pthread_self();//get thread id
    printf("%s pid %lu tid %lu (0x%lx)\n", s, (unsigned long)pid,
            (unsigned long)tid, (unsigned long)tid);
}

void* thr_fn(void* args)
{
    printids("new thread:");
    return (void*)0;
}

int main()
{
    int err;
    err = pthread_create(&ntid, NULL, thr_fn, NULL);
    if (err != 0)
        perror("can't create thread!");
    printids("main thread:");
    sleep(1);

    exit(0);

}
main thread: pid 9013 tid 139851889776448 (0x7f31ce361740)
new thread: pid 9013 tid 139851881416448 (0x7f31cdb68700)

5、线程终止

进程中调用exit_Exit_exit,那么整个进程会终止。
单个线程3种方式退出:

  • 从启动例程返回
  • 被同一进程的其他线程取消
  • 调用thread_exit
#include 
void pthread_exit(void *rval_ptr);
* `rval_ptr`是一个无类型指针,与传给启动例程的单个参数类似。
* 进程中其他线程可以访问该指针
```
#include 
int pthread_jion(pthread_t thread, void **rval_ptr);
//返回值:若成功,返回0;否则,返回错误编号
```

* 调用线程将一直阻塞,直到指定的线程调用pthread_exit、从启动例程中返回或者被取消。如果线程简单的从它的启动例程返回,rval_ptr就包含返回码。如果线程被取消,由rval_ptr指定的内存单元就设设置为PTHREAD_CANCELED。如果对线程返回值不感兴趣,那么可以把rval_ptr设置为NULL

例子:获得线程的退出状态

#include 
#include 
#include 
#include 

void* thr_fn1(void* arg)
{
    printf("thread 1 returning\n");
    return (void*)1;
}

void* thr_fn2(void* arg)
{
    printf("thread 2 returning\n");
    pthread_exit((void*)2);
}

int main()
{
    int err;
    pthread_t tid1, tid2;
    void *tret;
    err = pthread_create(&tid1, NULL, thr_fn1, NULL);
    if (err != 0)
    {
        perror("can't create thread 1.\n");
        exit(1);
    }
    err = pthread_create(&tid2, NULL, thr_fn2, NULL);
    if (err != 0)
    {
        perror("can't create thread 2.\n");
        exit(1);
    }

    err = pthread_join(tid1, &tret); // get thread1 return code
    if (err != 0)
    {
        perror("can't join with thread1.\n");
        exit(1);
    }
    printf("thread1 exit code : %ld\n", (long)tret);
    err = pthread_join(tid2, &tret);
    if (err != 0)
    {
        perror("can't join thread 2.\n");
        exit(1);
    }
    printf("thread 2 exit code is %ld\n", (long)tret);

    exit(0);
}

输出:

thread 2 returning
thread 1 returning
thread1 exit code : 1
thread 2 exit code is 2

线程可以通过pthread_cancel函数来请求取消同一进程中的其他线程。

#include 
int pthread_cancel(pthread_t tid)
//返回值:成功返回0,错误返回错误码
  • 默认情况下,该函数会使由tid标识的线程的行为表现为如同调用了参数为PTHREAD_CANCELEDpthread_exit函数。

线程可以安排它退出的函数,这与进程在退出时可以用atexit函数一样。这样的函数为线程清理函数。注意:一个线程可以有多个线程清理函数,处理程序记录在栈上,这与它们注册时相反。

#include

void pthread_cleanup_push(void (*rtn)(void*), void* arg);
void pthread_cleanup_pop(int execute);

当线程执行以下动作时,清理函数rtn是由pthread_cleanup_push函数调度的,调用是只有一个参数arg:

  • 调用pthread_exit时;
  • 相应取消请求时;
  • 用非零execute参数调用pthread_cleanup_pop时。

如果execute参数设置为0,清理函数将不被调用。不管发生哪种情况。pthread_cleanup_pop都将删除上次pthread_cleanup_push调用建立的情路处理程序。

一个程序清理处理程序:

#include 
#include 
#include 
#include 

void cleanup(void *arg)
{
    printf("clearnup: %s", (char*) arg);
}

void* thr_fn1(void* arg)
{
    printf("thread 1 start.\n");
    pthread_cleanup_push(cleanup, "thread 1 first handle");
    pthread_cleanup_push(cleanup, "thread 1 second handle");
    printf("thread1 push complete\n");
    if (arg)
        return (void*)1;

    pthread_cleanup_pop(0);
    pthread_cleanup_pop(0);

    return (void*)1;
}

void* thr_fn2(void* arg)
{
    printf("thread 2 start.\n");
    pthread_cleanup_push(cleanup, "thread 2 first handle");
    pthread_cleanup_push(cleanup, "thread 2 second handle");
    if (arg)
        return (void*)1;

    pthread_cleanup_pop(0);
    pthread_cleanup_pop(0);

    return (void*)1;
}

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

    err = pthread_create(&tid1, NULL, thr_fn1, (void*)1);
    if (err != 0)
    {
        printf("create thread1 error!\n");
        exit(1);
    }
    err = pthread_create(&tid2, NULL, thr_fn2, (void*)1);
    if (err != 0)
    {
        perror("create thread2 error!\n");
        exit(1);
    }
    err = pthread_join(tid1, &tret);
    if (err != 0)
    {
        perror("join thread 1 error!\n");
        exit(1);
    }
    printf("thread 1 exit code : %ld\n",(long)tret );
    err = pthread_join(tid2, &tret);
    if (err != 0)
    {
        perror("join thread 2 error!\n");
        exit(1);
    }
    printf("thread 2 exit code : %ld\n", (long)tret);

    exit(0);
}

默认情况下,线程的终止状态会保持到对该线程调用pthread_join。如果线程已经分离,则线程底层存储资源可以在线程终止时立即收回。可以用函数pthread_detach分离

#include 
int pthread_detach(pthread_t tid);

6、线程同步

例子:如果有两个线程A和B,对全局变量a=0执行相同的操作a++,那么a最后的结果会是多少呢?如果是顺序执行,A和B不存在竞争关系,那么就是2,但是由于a++不是原子操作。需要3步完成。1.从内存读入寄存器;2.寄存器中对变量增加;3.把新值写会内存。这样可能为1,也可能为2.

互斥量(Mutex)

为解决上述问题,我们需要使用pthread的互斥接口保护数据,保证在同一时间只有一个线程访问数据。
互斥锁的使用过程为:

  • 访问共享资源前加锁
  • 开始访问共享资源
  • 访问加速后解锁

加锁后其他线程会被阻塞直到当前线程释放该互斥锁。

那么互斥锁是怎么样的呢?
结构体pthread_mutex_t表示互斥变量。使用互斥变量之前,必须首先对它进行初始化,可以把它设置为常量PTHREAD_MUTEX_INITIALIZER(静态),也可以通过调用pthread_mutex_init函数进行初始化。如果动态分配互斥量,在释放内存前需要调用pthread_mutex_destroy

#include 
int pthread_mutex_init(pthread_mutex_t *restrict mutex,
                    const pthread_mutexattr_t *restrict attr);
int pthread_mutex_destroy(pthread_mutex_t *mutex);
//两个函数的返回值:若成功,返回0;否则,返回错误编号
  • 要用默认的属性初始化互斥量,把attr设为NULL

互斥量加锁和解锁的函数

#include 
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
//所有函数的返回值:若成功,返回0;否则,返回错误编号
  • pthread_mutex_lock : 如果互斥量已经被其他线程加锁,那么会阻塞,直到该互斥量被解锁再对互斥量加锁。
  • pthread_mutex_trylock:如果互斥量已经被其他线程加锁,则函数返回错误。如果未被加锁,那么就对互斥量加锁。
  • pthread_mutex_unlock:解锁

下面是一个用mutex来保护数据结构的例子:

#include 
#include 
#include 
#include 
#include 

struct foo
{
    int f_count;
    pthread_mutex_t f_lock;
    int f_id;
};

struct foo* foo_alloc(int id)
{
    struct foo *fp;

    if ((fp = (struct foo*)(malloc(sizeof(struct foo)))) != NULL)
    {
        fp->f_count = 1;
        fp->f_id = id;
        if (pthread_mutex_init(&fp->f_lock, NULL) != 0)
        {
            free(fp);
            return NULL;
        }
    }

    return fp;
}

void foo_rela(struct foo* fp)
{
    pthread_mutex_lock(&fp->f_lock);
    if (--fp->f_count == 0)
    {
        pthread_mutex_unlock(&fp->f_lock);
        pthread_mutex_destroy(&fp->f_lock);
        free(fp);
    }
    else
    {
        pthread_mutex_unlock(&fp->f_lock);
    }
}

避免死锁

  1. 线程试图对同一个互斥量加锁两次,那么它自身就会陷入死锁状态。
pthread_mutex_lock(mutex);
pthread_mutex_lock(mutex);
pthread_mutex_unlock(mutex);
pthread_mutex_unlock(mutex);
  1. 程序中使用一个以上的互斥量时,如果允许一个线程一直占有第一个互斥量,并且试图锁住第二个互斥量时处于阻塞状态,但是拥有第二个互斥量的线程也在试图锁住第一个互斥量。因为两个线程都在相互请求另一个线程拥有的资源,所以这两个线程都无法向前运行,于是就产生死锁。

可以先释放占有的锁,过一段时间再试

pthread_mutex_timedlock

int pthread_mutex_timedlock(pthread_mutex_t *restrict mutex,const struct timespec *restrict tsptr);
  • pthread_mutex_timedlock:尝试加锁,阻塞(最多阻塞到时间点tsptr),如果在tsptr还无法加锁,则返回错误。

下面是一个例子:

#include 
#include 
#include 
#include 
#include 
#include 

int main()
{
    int err;
    struct timespec tout;
    struct tm *tmp;
    char buf[64];
    pthread_mutex_t  lock = PTHREAD_MUTEX_INITIALIZER;

    pthread_mutex_lock(&lock);
    printf("mutex is locked!\n");
    clock_gettime(CLOCK_REALTIME, &tout);
    tmp = localtime(&tout.tv_sec);
    strftime(buf, sizeof(buf), "%r", tmp);
    printf("current time is %s\n", buf);
    tout.tv_sec +=10;
    err = pthread_mutex_timedlock(&lock, &tout);
    clock_gettime(CLOCK_REALTIME, &tout);
    tmp = localtime(&tout.tv_sec);
    strftime(buf, sizeof(buf), "%r", tmp);
    printf("the time is now %s\n", buf);
    if (err == 0)
    {
        printf("mutex locked again!\n");
    }
    else
    {
        printf("Can't lock mutex again:%s\n", strerror(err));
    }

    exit(0);
}
mutex is locked!
current time is 08:45:01 PM
the time is now 08:45:11 PM
Can't lock mutex again:Connection timed out

读写锁

读写锁对于为资源共享提高了并行性。
读写锁的策略:

  • 读写锁在读加锁状态时,任何其他的读加锁都可以获得访问权
  • 读写锁在写加锁状态时,不允许任何对该资源加锁
  • 只有在没有加锁时才可以写加锁。

读写锁必须初始化

初始化和销毁函数:

#include 
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,
            const pthread_rwlockattr_t *restrict attr);
int pthread_rwlock_destory(pthread_rwlock_t *rwlock);
//两个函数的返回值:若成功,返回0;否则,返回错误编号

加锁和解锁函数

#include 
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;否则,返回错误编号
  • pthread_rwlock_rdlock读加锁
  • pthread_rwlock_wrlock写加锁
  • pthread_rwlock_unlock解锁

读写锁原语的条件版本

#include 
int pthread_rwlock_trylock(pthread_rwlock_t *rwlock);
int pthread_rwlock_trylcok(pthread_rwlock_t *rwlock);
//两个函数的返回值:若成功,返回0;否则,返回错误编码
  • 可以获取时,这两个函数返回0。否则,它们返回错误EBUSY。

带有超时的读写锁

#include 
#include 
int pthread_rwlock_timedrdlock(
                    pthread_rwlock_t *restrict rwlock,
                    const struct timespec *restrict tsptr);
int pthread_rwlock_timedwrlock(
                    pthread_rwlock_t *restrict rwlock,
                    const struct timespec *restrict tsptr);
//两个函数的返回值:若成功,返回0;否则,返回错误编号

条件变量

参考:
http://www.cnblogs.com/newlist/archive/2012/02/11/2346284.html
http://www.wuzesheng.com/?p=1668

条件变量(Condtion Variable)是在多线程程序中用来实现“等待->唤醒”逻辑常用的方法。举个简单的例子,应用程序A中包含两个线程t1和t2。t1需要在bool变量test_cond为true时才能继续执行,而test_cond的值是由t2来改变的,这种情况下,如何来写程序呢?可供选择的方案有两种:

第一种是t1定时的去轮询变量test_cond,如果test_cond为false,则继续休眠;如果test_cond为true,则开始执行。
第二种就是上面提到的条件变量,t1在test_cond为false时调用cond_wait进行等待,t2在改变test_cond的值后,调用cond_signal,唤醒在等待中的t1,告诉t1 test_cond的值变了,这样t1便可继续往下执行。
很明显,上面两种方案中,第二种方案是比较优的。在第一种方案中,在每次轮询时,如果t1休眠的时间比较短,会导致cpu浪费很厉害;如果t1休眠的时间比较长,又会导致应用逻辑处理不够及时,致使应用程序性能下降。第二种方案就是为了解决轮询的弊端而生的。

与互斥锁不同,条件变量是用来等待而不是用来上锁的。条件变量用来自动阻塞一个线程,直到某特殊情况发生为止。通常条件变量和互斥锁同时使用。

条件变量可以使我们可以睡眠等待某种条件出现。条件变量是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:一个线程等待”条件变量的条件成立”而挂起;另一个线程使”条件成立”(给出条件成立信号)。条件的检测是在互斥锁的保护下进行的。如果一个条件为假,一个线程自动阻塞,并释放等待状态改变的互斥锁。如果另一个线程改变了条件,它发信号给关联的条件变量,唤醒一个或多个等待它的线程,重新获得互斥锁,重新评价条件。如果两进程共享可读写的内存,条件变量可以被用来实现这两进程间的线程同步。

条件变量分为两部分: 条件和变量. 条件本身是由互斥量保护的. 线程在改变条件状态前先要锁住互斥量. 它利用线程间共享的全局变量进行同步的一种机制。

条件变量的结构体为pthread_cond_t

条件变量的初始化:

#include 
int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr);
int pthread_cond_destroy(pthread_cond_t *cond);
//返回值:成功返回0,否则返回错误码
  • 可以是使用PTHREAD_COND_INITIALIZER静态初始化。

使用pthread_cond_wait等待条件变量变为真。如果在给定的时间内条件不能满足,则会生成错误码。

#include 
int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);
int pthread_cond_timedwait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex,
const struct timespec *restrict tsptr);
  • pthread_cond_wait函数会发生下面过程:
    1. 解锁互斥量
    2. 阻塞等待条件变量唤醒
    3. 加锁互斥量

唤醒函数:

#include 
int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);

使用:
(1)初始化.init()或者pthread_cond_t cond=PTHREAD_COND_INITIALIER;属性置为NULL
(2)等待条件成立.pthread_wait,pthread_timewait.wait()释放锁,并阻塞,等待条件变量为真
(3)激活条件变量:pthread_cond_signal,pthread_cond_broadcast(激活所有等待线程)
(4)清除条件变量:destroy;无线程等待,否则返回EBUSY

主线程:

  • 声明并初始化需要同步的全局数据或变量(例如”count“)
  • 声明并初始化一个条件变量对象
  • 声明并初始化一个与条件变量关联的互斥量
  • 创建线程A和B并开始运行

线程A:

  • 线程运转至某一个条件被触发(例如,”count“必须达到某个值)
  • 锁定相关联的互斥量并检查全局变量的值
  • 调用pthread_con_wait()阻塞线程等待线程B的信号。请注意,调用pthread_con_wait()以自动的原子方式(atomically)解锁相关联的互斥量,以便于可以被线程B使用。
  • 当收到信号时,唤醒线程。互斥量被以自动的原子方式被锁定。
  • 明确的解锁互斥量。
  • 继续

线程B:

  • 线程运转
  • 锁定相关联的互斥量
  • 更改线程A正在等待的全局变量的值
  • 检查线程A等待的变量值,如果满足条件,发信号给线程A
  • 解锁互斥量
  • 继续

一个例子:

// 使用条件变量的一个例子:
//
#include 
#include 
#include 
#include 

#define NUM_THREAD 3//线程数目
#define TCOUNT 5    //单线程轮询次数
#define COUNT_LIMIT 7//发送信号的次数
int count = 0; //全局累加量

pthread_mutex_t count_mutex;
pthread_cond_t count_threshold_cv;

//增加count
void *inc_count(void *t)
{
    int i;
    long my_id = (long)t;

    for (i = 0; i < TCOUNT; ++i)
    {
        pthread_mutex_lock(&count_mutex);
        count++;
        //检查count的值,满足条件就发送信号
       if (count < COUNT_LIMIT)
        {
            printf("inc_count(): thread %ld, count = %d Threshold reached. ",
                    my_id, count);
            //发送信号
            pthread_cond_signal(&count_threshold_cv);
            printf("Just sent signal.\n");
        }

       printf("inc_count() : thread %ld, count = %d, unlocking muext.\n",
               my_id, count);
       pthread_mutex_unlock(&count_mutex);

       sleep(1);
    }

    pthread_exit(NULL);
}

//等待线程
void *watch_count(void* t)
{
    long my_id = (long)t;
    printf("Starting watch_count(): thread %ld.\n", my_id);
    //锁定互斥量并等待信号,以原子的方式解锁互斥量。
    while ( count < COUNT_LIMIT)
    {
        pthread_mutex_lock(&count_mutex);
        printf("watch_count(): thread %ld go inot wait...\n", my_id);
        pthread_cond_wait(&count_threshold_cv, &count_mutex);
        printf("watch_count():thread %ld Condition signal received.\n", my_id);

        printf("watch_count():thread %ld count now = %d.\n", my_id, count);
        pthread_mutex_unlock(&count_mutex);
   }
    pthread_exit(NULL);
}

int main()
{
    int i;
    long t1 =1, t2 = 2, t3 = 3;

    pthread_t threads[3];
    pthread_attr_t attr;

    //初始化互斥量和条件变量对象
    pthread_attr_init(&attr);
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
    pthread_create(&threads[0], &attr, watch_count, (void*) t1);
    pthread_create(&threads[1], &attr, inc_count, (void*) t2);
    pthread_create(&threads[2], &attr, inc_count, (void*) t3);

    for (int i= 1; i < NUM_THREAD; ++i)
    {
        pthread_join(threads[i], NULL);
    }

    //发送信号个i额监听线程
    //
    pthread_cond_signal(&count_threshold_cv);
    pthread_join(threads[0], NULL);
    printf("Main():Waited on %d theaads, final value of ocunt = %d.Done\n",
            NUM_THREAD, count);

    //清理线程
    //
    pthread_attr_destroy(&attr);
    pthread_mutex_destroy(&count_mutex);
    pthread_cond_destroy(&count_threshold_cv);
    pthread_exit(NULL);
}
inc_count(): thread 3, count = 1 Threshold reached. Just sent signal.
inc_count() : thread 3, count = 1, unlocking muext.
inc_count(): thread 2, count = 2 Threshold reached. Just sent signal.
inc_count() : thread 2, count = 2, unlocking muext.
Starting watch_count(): thread 1.
watch_count(): thread 1 go inot wait...
inc_count(): thread 3, count = 3 Threshold reached. Just sent signal.
inc_count() : thread 3, count = 3, unlocking muext.
watch_count():thread 1 Condition signal received.
watch_count():thread 1 count now = 3.
watch_count(): thread 1 go inot wait...
inc_count(): thread 2, count = 4 Threshold reached. Just sent signal.
inc_count() : thread 2, count = 4, unlocking muext.
watch_count():thread 1 Condition signal received.
watch_count():thread 1 count now = 4.
watch_count(): thread 1 go inot wait...
inc_count(): thread 3, count = 5 Threshold reached. Just sent signal.
inc_count() : thread 3, count = 5, unlocking muext.
inc_count(): thread 2, count = 6 Threshold reached. Just sent signal.
inc_count() : thread 2, count = 6, unlocking muext.
watch_count():thread 1 Condition signal received.
watch_count():thread 1 count now = 6.
watch_count(): thread 1 go inot wait...
inc_count() : thread 2, count = 7, unlocking muext.
inc_count() : thread 3, count = 8, unlocking muext.
inc_count() : thread 3, count = 9, unlocking muext.
inc_count() : thread 2, count = 10, unlocking muext.
watch_count():thread 1 Condition signal received.
watch_count():thread 1 count now = 10.
Main():Waited on 3 theaads, final value of ocunt = 10.Done

自旋锁

自旋锁在获取锁之前一直处于忙等待状态。自旋锁的作用是提高加锁的效率。它一般用于使用者保持锁时间较短的情况。
自旋锁适合于保持时间非常短的情况,它可以在任何上下文使用。如果被保护的共享资源只在进程上下文访问,使用信号量保护该共享资源非常合适,如果对共享资源的访问时间非常短,自旋锁也可以。但是如果被保护的共享资源需要在中断上下文访问(包括底半部即中断处理句柄和顶半部即软中断),就必须使用自旋锁。自旋锁保持期间是抢占失效的,而信号量和读写信号量保持期间是可以被抢占的。自旋锁只有在内核可抢占或SMP(多处理器)的情况下才真正需要,在单CPU且不可抢占的内核下,自旋锁的所有操作都是空操作。另外格外注意一点:自旋锁不能递归使用。

自旋锁的一些函数:
初始化和销毁:

#include 
int pthread_spin_init(pthread_spinlock_t *lock,intpshared);
int pthread_spin_destroy(pthread_spinlock_t *lock);
//成功返回0,失败返回错误编码

加锁和解锁:

#include 
int pthread_spin_lock(pthread_spinlock_t *lock);
int pthread_spin_trylock(pthread_spinlock_t *lock);
int pthread_spin_unlock(pthread_spinlock_t *lock);
//成功返回0,失败返回错误码

屏障

屏障允许多个线程等待,直到所有合作的线程到达某个点。

初始化和销毁函数:

#include 
int pthread_barrier_init(pthread_barrier_t *restrictbarrier,const pthread_barrierattr_t *restrict attr,unsigned int count);
int pthread_barrier_destroy(pthread_barrier_t *barrier);
//成功返回0,失败返回错误码
  • count用于指定要等待的线程数目。

告知线程已完成工作,准备所有其他线程赶过来

#include 
int pthread_barrier_wait(pthread_barrier_t *barrier);
//成功返回0,失败返回错误码
  • 在初始化屏障计数未满足条件时,会进入休眠状态。
  • 最后一个线程调用该函数,满足了屏障计数条件,会唤醒所有的进程。

使用内存屏障的一个例子:

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define NTHR 8
#define NUMNUM 8000000L
#define TNUM (NUMNUM/NTHR)

long nums[NUMNUM];
long snums[NUMNUM];

pthread_barrier_t b;

extern void qsort(void*, size_t, size_t, int(*)(const void*, const void*));

int complong(const void* arg1, const void* arg2)
{
    long l1 = *(long *)arg1;
    long l2 = *(long *)arg2;

    if (l1 == l2)
        return 0;
    if (l1 < l2)
        return -1;
    return 1;
}

void *thr_fn(void *arg)
{
    long idx = (long)arg;
    qsort(&nums[idx], TNUM, sizeof(long), complong);
    pthread_barrier_wait(&b);

    return (void*)0;
}

void merge()
{
    long idx[NTHR];
    long i, minidx, sidx, num;
    for(i = 0; i < NTHR; ++i)
    {
        idx[i] = i * TNUM;
    }

    for (sidx = 0; sidx < NUMNUM; ++sidx)
    {
        num = LONG_MAX;
        for (i = 0; i < NTHR; ++i)
        {
            if ((idx[i] < (i+1)*TNUM) && (nums[idx[i]] < num))
            {
                num = nums[idx[i]];
                minidx = i;
            }
        }
        snums[sidx] = nums[idx[minidx]];
        idx[minidx]++;
    }
}

int main()
{
    unsigned long i;
    struct timeval start, end;
    long long startusec, endusec;
    double elapsed;
    int err;
    pthread_t tid;

    srandom(1);

    for (int i = 0; i < NUMNUM; ++i)
        nums[i] = random();

    gettimeofday(&start, NULL);
    pthread_barrier_init(&b, NULL, NTHR+1);
    for (int i = 0; i < NTHR; ++i)
    {
        err = pthread_create(&tid, NULL, thr_fn, (void*)(i * TNUM));
        if (err != 0)
        {
            perror("Can't create thread!\n");
            exit(1);
        }
    }

    pthread_barrier_wait(&b);
    merge();
    gettimeofday(&end, NULL);

    startusec = start.tv_sec * 1000000 + start.tv_usec;
    endusec = end.tv_sec * 1000000 + end.tv_usec;

    elapsed = (double)(endusec - startusec)/1000000;
    printf("sort took %.4f seconds\n", elapsed);
//    for(i = 0;  i< NUMNUM; ++i)
//        printf("%ld\n", snums[i]);
    exit(0);
}

你可能感兴趣的:(UNIX环境高级编程)