UNIX环境高级编程——线程

线程

线程的概念

典型的UNIX进程可以看成只有一个控制线程:一个进程在同一时刻只做一件事。有了多个控制线程后,在程序设计时可以把进程设计成在同一时刻能够做不止一件事,每
个线程处理各自独立的任务。

线程的优点

  • 通过为每种事件类型分配单独的处理线程,可以简化处理异步事件的代码。每个线程在进行事件处理时可以采用同步编程模式,同步编程模式要比异步编程模式简单得多。
  • 统一进程多个线程可以自动的共享相同的存储地址空间和文件描述符。
  • 有些问题可以分解从而提高整个程序的吞吐量。在只有一个控制线程的情况下,一个单线程进程要完成多个任务,只需要把这些任务串行化。但是有多个控制线程时,相互独立的任务的处理就可以交叉进行,此时只需要为每个任务分配一个单独的线程。当然只有在两个任务的处理过程相互不依赖的情况下,两个任务才可以交叉执行。
  • 交互的程序同样可以通过多线程来改善响应事件,多线程可以把程序中处理用户输入输出的部分与其他部分分开。

有些人把多线程的程序设计与处理器或多核系统联系起来。但是即使程序运行在单处理器上,也能得到多线程编程模型的好处。处理器的数量并不影响程序结构,所以不管处理器的个数多少,程序都可以通过使用线程得以简化。而且,即使多线程程序在串行化任务时不得不阻塞,由于某些线程在阻塞时还有另一些线程可以运行,所以多线程在单处理器上运行还是可以改善响应时间和吞吐量。

每个线程都含有表示执行环境所必须的信息,其中包括进程中标识线程的线程ID, 一组寄存器值、栈、调度优先级和策略、信号屏蔽字、errno变量以及线程私有数据。一个进程的所有信息对该进程的所有线程都是共享的,包括可执行代码、程序的全局内核和堆内存、栈以及文件描述符

线程标识

  • 进程ID在整个系统中是唯一的,但线程ID不同,线程ID只有在它所属的进程上下文中才有意义。
  • 线程ID的类型是: pthread_t,是一个结构体数据类型,所以可移植操作系统实现不能把它作为整数处理。因此必须使用一个函数对两个线程ID进行比较:
#include 

int pthread_equal(pthread_t tid1, pthread_t tid2);

// 若相等,返回非0数值;否则,返回0
  • pthread_self

使用 pthread_t 数据类型的 后果是不能用 一种可移植的方式打印该数据类型的值。在程序调试中打印线程ID是非常有用的,而在其他情况下通常不需要打印线程ID。最坏的情况是有可能出现不可移植的调试代码,当然这也算不上是很大的局限性。

#include 

pthread_t pthread_self(void); // 返回调用线程的线程ID

线程创建

在传统的UNIX进程模型中,每个线程只有一个控制线程。从概念上讲,这与给予线程的模型中每个进程只含有一个线程是相同的。在POSIX线程(pthread)的情况下,程序开始运行时,它是以单进程中的单个控制线程启动的。在创建多个控制线程以前,程序的行为与传统的进程并没有什么区别。新增的线程可以通过调用 pthread_create 函数创建。

#include 

int pthread_create(pthread_t *restrict tidp,
                              const pthread_attr_t *restrict attr,
                              void *(*start_rtn)(void *),
                              void *restrict arg);
// 返回值:成功返回0;否则返回错误编号
  • tidp:新创建的线程ID会被设置成tidp指向的内存单元。
  • attr:用于定制各种不能的线程属性,默认为NULL
  • start_rtn:新创建的线程从start_rtn函数的地址开始运行,该函数只有一个void类型的指针参数即arg,如果start_rtn需要多个参数,可以将参数放入一个结构中,然后将结构的地址作为arg传入。

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

注意:pthread函数在调用失败时通常会返回错误代码,他们并不像其他的 POSIX 函数一样设置 errno。每个线程都提供 errno 的副本,这只是为了与使用 errno 的现有函数兼容。在线程中,从函数中返回错误代码更为清晰整洁,不需要依赖那些随着函数执行不断变化的全局状态,这样可以把错误的方位限制在引起出错的函数中。

  • 实例
#include 
#include 
#include 
#include 
#include 

pthread_t ntid;

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

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

    printf("%s pid %lu tid %lu (0x%lx)\n", s, (unsigned long)pid, (unsigned long)tid, (unsigned long)tid);
}

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

int main()
{
    int err;

    err = pthread_create(&ntid, NULL, thr_fn, NULL);
    if (err!=0) {
        printf("can't create thread\n");
        exit(1);
    }
    printids("main thread:");
    sleep(2);
    exit(0);
}

// gcc 1.c -lpthread

/*
 * 执行结果:
 * main thread: pid 1969 tid 140618274715392 (0x7fe43e503700)
 * new thread: pid 1969 tid 140618266445568 (0x7fe43dd20700)
*/

该实例需要处理主线程和新线程之间的竞争。

  • 主线程需要休眠,如果主线程不休眠,它可能退出,这样新线程就没有继续运行,这个进程就终止了。
  • 通过 pthread_self 函数获取线程ID,而不是读取变量 ntid。 ntid 变量在新线程中不能安全的使用,如果新线程在主线程调用 pthread_create 返回之前就运行了,那么新线程看到的就是未经初始化的 ntid 内容,这个内容并不是正确的线程ID。

线程终止

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

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

  • 线程可以简单地从启动例程中返回,返回值是线程的退出码。
  • 线程可以被同一进程中的其他线程取消。
  • 线程调用 pthread_exit .
#include 

void pthread_exit(void *rval_ptr);
// rval_ptr : 进程中的其他线程可以通过调用 pthread_join 函数访问这个指针。

int pthread_join(pthread_t thread, void **rval_ptr);
// 返回值:成功返回0;否则返回错误编码

调用线程将一直阻塞,直到制定的线程调用 pthread_exit、从启动例程中返回或者被取消。如果线程简单地从它的返回例程中返回,rval_ptr 就包含返回码。如果线程被取消,由 rval_ptr 指定的内存单元就被设置为 PTHREAD_CANCELED。

可以通过调用 pthread_join 自动把线程置于分离状态,这样资源就可以恢复。如果线程已处于分离状态, pthread_join 调用会失败,返回 EINVAL,尽管这种行为是与具体实现相关的。

如果对线程的返回值并不感兴趣,那么可以把 rval_ptr 设置为 NULL。这时,调用 pthread_join 可以等待指定的线程终止,但并不获取线程的终止状态。

实例:

#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");
    return ((void*)2);
}

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

    err = pthread_create(&tid1, NULL, thr_fn1, NULL);
    if (err != 0) {
        printf("Can't create thread 1\n");
        exit(0);
    }
    err = pthread_create(&tid2, NULL, thr_fn2, NULL);
    if (err != 0) {
        printf("Can't create thread 2\n");
        exit(0);
    }

    err = pthread_join(tid1, &tret);
    if (err != 0) {
        printf("Can't join with thread 1\n");
        exit(0);
    }
    printf("thread 1 exit code: %ld\n", (long)tret);

    err = pthread_join(tid2, &tret);
    if (err != 0) {
        printf("Can't join with thread 2\n");
        exit(0);
    }
    printf("thread 2 exit code: %ld\n", (long)tret);

    exit(0);
}
/*
 * 打印结果:
 * thread 1 returning...
 * thread 2 returning...
 * thread 1 exit code: 1
 * thread 2 exit code: 2
*/

// 通过 pthread_join 可以获取该线程的退出状态.

pthread_create 和 pthread_exit 函数的无类型指针参数可以传递的值不止一个,这个指针可以传递包含复杂信息的结构体地址,但是注意,这个结构所使用的内存在调用者完成调用以后必须仍然是有效的.例如,在调用线程的栈上分配了该结构,那么其他的线程在使用这个结构时内存内容可能已经改变了.又如,线程在自己的栈上分配了一个结构,然后把指向这个结构的指针传给 pthread_exit ,那么调用 pthread_join 的线程试图使用该结构时,这个栈有可能已经被撤销,这块内存也另做他用.

实例:

#include 
#include 
#include 

struct foo {
    int a, b, c, d;
};

void printfoo(const char *s, const struct foo *fp)
{
    printf("%s", s);
    printf("  structure at 0x%lx\n", (unsigned long)fp);
    printf("  foo.a = %d\n", fp->a);
    printf("  foo.b = %d\n", fp->b);
    printf("  foo.c = %d\n", fp->c);
    printf("  foo.d = %d\n", fp->d);
}

void *thr_fn1(void *arg)
{
    struct foo foo = {1, 2, 3, 4};

    printfoo("thread 1: \n", &foo);
    pthread_exit((void*)&foo);
}

void *thr_fn2(void *arg)
{
    printf("thread 2: ID is %lu\n", (unsigned long)pthread_self());
    pthread_exit((void *)0);
}

int main(void)
{
    int err;
    pthread_t tid1, tid2;
    struct foo *fp;

    err = pthread_create(&tid1, NULL, thr_fn1, NULL);
    if (err != 0) {
        printf("Can't create thread 1\n");
        exit(0);
    }

    err = pthread_join(tid1, (void *)&fp);
    if (err != 0) {
        printf("Can't join with thread 1\n");
        exit(0);
    }
    sleep(1);

    printfoo("second: \n", fp);

    printf("parent starting second thread\n");
    
    err = pthread_create(&tid2, NULL, thr_fn2, NULL);
    if (err != 0) {
        printf("Can't create thread 2\n");
        exit(0);
    }
    sleep(1);
    printfoo("parent: \n", fp);

    exit(0);
}
/*
 * 执行结果:
 thread 1: 
  structure at 0x7f5148e1ff30
  foo.a = 1
  foo.b = 2
  foo.c = 3
  foo.d = 4
second: 
  structure at 0x7f5148e1ff30
  foo.a = 0
  foo.b = 0
  foo.c = 1224302914
  foo.d = 32593
parent starting second thread
thread 2: ID is 139987091851008
parent: 
  structure at 0x7f5148e1ff30
  foo.a = 1226709248
  foo.b = 32593
  foo.c = 1224302914
  foo.d = 32593
*/
// 线程1在返回 foo 时,foo分配在栈上,当线程退出时,栈被回收,故foo已经发生变化,在主线程中访问 foo 必然是错误的.
  • pthread_cancel : 取消同一进程中的其他线程
#include 

int pthread_cancel(pthread_t tid);

// 返回值:成功返回0;否则返回错误编号

默认情况下,pthread_cancel函数会使得由 tid 标识的线程的行为表现为如同调用了参数为 PTHREAD_CANCELED 的 pthread_exit 函数;但是,线程可以选择忽略取消或者控制如何被取消.注意: pthread_cancel 并不等待线程终止,它仅仅提出请求.

线程可以安排它退出时需要调用的函数,这与进程在退出时可以用 atexit 函数安排退出是类似的.这与的函数称为 线程清理程序(thread cleanup handler).一个线程可以建立多个清理程序.处理程序记录在栈中,也就是说,他们的执行顺序与他们注册时相反.

#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 调用建立的清理处理程序.

这些函数有一个限制,由于他们可以实现为宏,所以必须在与线程相同的作用域中以匹配对的形式使用.pthread_cleanup_push的宏可以包含字符 {, 这种情况下,在 pthread_cleanup_pop 的定义中要有对应的匹配字符 }.

#include 
#include 
#include 

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

void *thr_fn1(void *arg)
{
    printf("thread 1 start...\n");

    pthread_cleanup_push(cleanup, "thread 1 first handler");
    pthread_cleanup_push(cleanup, "thread 1 second handler");
    printf("thread 1 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 handler");
    pthread_cleanup_push(cleanup, "thread 2 second handler");
    printf("thread 2 push complete\n");

    if (arg) {
        pthread_exit((void*)2);
    }

    pthread_cleanup_pop(0);
    pthread_cleanup_pop(0);

    pthread_exit((void*)2);
}

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

    err = pthread_create(&tid1, NULL, thr_fn1, (void *)1);
    if (err != 0) {
        printf("Can't create thread 1\n");
        exit(0);
    }
    err = pthread_create(&tid2, NULL, thr_fn2, (void *)1);
    if (err != 0) {
        printf("Can't create thread 2\n");
        exit(0);
    }

    err = pthread_join(tid1, &tret);
    if (err != 0) {
        printf("Can't join with thread 1\n");
        exit(0);
    }
    printf("thread 1 exit code: %ld\n", (long)tret);

    err = pthread_join(tid2, &tret);
    if (err != 0) {
        printf("Can't join with thread 2\n");
        exit(0);
    }
    printf("thread 2 exit code: %ld\n", (long)tret);

    exit(0);
}

/*
 * 执行结果:
thread 1 start...
thread 1 push complete
thread 2 start...
thread 2 push complete
thread 1 exit code: 1
cleanup: thread 2 second handler
cleanup: thread 2 first handler
thread 2 exit code: 2
*/

如果线程是从启动里程中返回而终止的话,它的清理程序就不会被调用.清理程序是按照注册相反的顺序被调用的.

进程原语 线程原语 描述
fork pthread_create 创建新的控制流
exit pthread_exit 从现有的控制流中退出
waitpid pthread_join 从控制流中得到退出状态
atexit pthrread_cancel_push 注册在退出控制流时的调用的函数
getpid pthread_self 获取控制流ID
abort pthread_cancel 请求控制流的非正常退出

默认情况下,线程的终止状态会保存知道对该线程调用 pthread_join .如果线程已经被分离,线程的底层存储资源可以再线程终止时立即被回收.在线程被分离后,我们不能用 pthread_join 函数等待它的终止状态,因为对分离状态的线程调用 pthread_join 会产生未定义行为.可以调用 pthread_detach 分离线程.

#include 

int pthread_detach(pthread_t tid);
// 返回值:成功返回0;否则返回错误编号

线程同步

互斥量

  • 可以使用 pthread 的互斥接口来保护数据,确保同一时间只有一个线程访问数据.

  • 互斥量(mutex)本质上是一把锁,在访问共享资源前对互斥量进行加锁,在访问完成后释放互斥量.

  • 对互斥量进行加锁后,任何其他试图再次对互斥量加锁的线程都会被阻塞直到当前线程释放该互斥锁.

  • 如果释放互斥量时有一个以上的线程阻塞,那么所有该锁上的阻塞线程都会变成可执行状态,第一个运行的线程就可以对互斥量加锁,其他线程就会看到互斥量依然锁着,只能回去再次等待它重新变为可用.

  • 这种方式下,每次只有一个线程可以向前执行.

互斥量是用 pthread_mutex_t 数据类型表示.在使用之前需要进行初始化,可以把它设置为常量 PTHREAD_MUTEX_INITIALIZER (只适用于静态分配的互斥量),也可以通过调用 pthread_mutex_init 函数进行初始化.如果动态分配互斥量,在释放内存前需要调用 pthread_mutex_destory.

#include 

int pthread_mutex_init(pthread_mutex_t *restrict mutex,
                        const pthread_mutexattr_t *restrict attr); // 默认 attr = NULL
int pthread_mutex_destory(pthread_mutex_t *mutex);

// 返回值:成功返回0,否则返回错误编号
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,否则返回错误编号
struct foo {
    int f_count;
    pthread_mutex_t f_lock;
    int f_id;
};

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

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

    return fp;
}

void foo_hold(struct foo *fp)
{
    pthread_mutex_lock(&fp->f_lock);
    fp->f_count++;
    pthread_mutex_unlock(&fp->f_lock);
}

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

避免死锁

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

pthread_mutex_timedlock

'''c
int pthread_mutex_timedlock(pthread_mutex_t *restrict mutex,
const struct timespec *restrict tsptr); // 超时返回 ETIMEOUT

// 返回值:成功返回0,否则返回错误编号.
'''

读写锁

  • 读写锁(reader-writer lock)与互斥量类似,不过读写锁允许更高的并行性.
  • 互斥量要么是锁住状态,要么就是不加锁状态,而且一次只有一个线程可以对其加锁.
  • 读写锁可以有3中状态:读模式下加锁,写模式下加锁,不加锁状态.一次只有一个线程可以占有写模式的读写锁,但是多个线程可以同时占有读模式的读写锁.
  • 当读写锁是写加锁状态时,在这个锁被解锁之前,所有试图对这个锁加锁的线程都会被阻塞.
  • 当读写锁在读加锁状态时,所有试图以度模式对它进行加锁的线程都可以得以访问权,但是任何希望以写模式对此锁进行加锁的线程都会阻塞,直到所有线程释放他们的读锁为止.
虽然各操作系统对读写锁的实现各不相同,但当读写锁处于读模式锁住状态时,而这时有一个线程试图以写模式获取锁时,读写锁通常会阻塞随后的读模式锁的请求.
这样可以避免读模式锁的长期占用,而等待的写模式锁请求一直得不到满足.
  • 适用场景:对数据的读的次数远大于写的情况.
  • 读写锁也叫 共享互斥锁(shared-exclusive lock).
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,
                        const pthread_rwlockattr_t *restrict *restrict attr);
int pthread_rwlock_destory(pthread_rwlock_t *rwlock);
// 返回值:成功返回0,否则返回错误编号
  • 静态读写锁:PTHREAD_RWLOCK_INITIALIZER
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 的返回值.

int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
// 返回值:成功返回0,否则返回错误编号

带有超时的读写锁

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;否则返回错误编号

条件变量

  • 条件本身是由互斥量保护的.线程在该表条件状态之前必须首先锁住互斥量.
  • 使用条件变量之前,必须先对它初始化.
int pthread_cond_init(pthread_cond_t *restrict cond,
                    const pthread_condattr_t *restrict attr);
int pthread_cond_destory(pthread_cond_t *cond);
// 返回值:成功返回0;否则返回错误编号
  • 静态变量: PTHREAD_COND_INITIALIZER.
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); // 超时返回 ETIMEDOUT
// 返回值:成功返回0;否则返回错误编号
  • 传递给 pthread_cond_wait 的互斥量对条件进行保护.
  • pthread_cond_wait 等待期间,释放互斥量. pthread_cond_wait 返回后,互斥量重新被锁住.
#include 
#include 

void maketimeout(struct timespec *tsp, long minutes)
{
    struct timeval now;

    /* get the current time */
    gettimeofday(&now, NULL);
    tsp->tv_sec = now.tv_sec;
    tsp->tv_nsec = now.tv_usec * 1000; // usec to nsec
    tsp->tv_sec += minutes * 60;
}
int pthread_cond_signal(pthread_cond_t *cond);   // 唤醒一个等待该条件的线程
int ptrhead_cond_broadcast(pthread_cond_t *cond); // 唤醒等待该条件的所有线程
// 返回值:成功返回0;否则返回错误编号

自旋锁

  • 自旋锁和互斥量类似,但是它不能通过睡眠使进程阻塞,而是在获取锁之前一尺处于忙阻塞状态.

  • 适用情况:锁被持有的时间短,而且线程并不希望在重新调度上花费太多的成本.

  • 自旋锁用在非抢占式内核中是非常有用的:除了提供互斥机制外,他们会阻塞终端,这样中断处理程序就不会让系统陷入死锁状态.因为它需要获取已被加锁的自旋锁.这种类型的内核中,中断处理程序不能休眠,因此他们能用的同步原语只能是自旋锁.

  • 在用户层,自旋锁并不是非常有用的,除非运行在不允许抢占的实时调度类中.

  • 运行在分时调度类中的用户线程在两种情况下可以被取消调度:当他们的时间片到期时,或者具有更高优先级的线程就绪变成可运行时.在这些情况下,如果线程拥有自旋锁,他就会进入休眠状态,阻塞在锁上的其他线程自旋的事件可能会比预期的时间更长.

  • 很多互斥量的实现非常高效,以至于应用程序采用互斥锁的性能与曾经采用过自旋锁的性能基本相同.事实上,有些互斥量的实现在试图获取互斥量时会自旋一小段时间,只有自选计数达到某一阈值时才会休眠.这些因素,加上现代处理器的进步,使得上下文切换越来越快,也使得自旋锁只有在某些特定的情况下有用.

int pthread_spin_init(pthread_spinlock_t *lock, int pshared);
int pthread_spin_destory(pthread_spinlock_t *lock);
// 返回值:成功返回0;否则返回错误编号

// pshared: 表示进程共享属性,表明自旋锁是如何获取的. PTHREAD_PROCESS_SHARED 则自旋锁能被可以访问锁底层内存的线程所获取,几遍那些线程属于不同的进程.
// PTHREAD_PROCESS_PRIVATE: 自旋锁只能被初始化该锁的进程内部的线程访问.
int pthread_spin_lock(pthread_spinlock_t *lock);
int ptrhead_spin_trylock(pthread_spinlock_t *lock);
int pthread_spin_unlock(pthread_spinlock_t *lock);
// 返回值:成功返回0;否则返回错误编号

不要调用在持有自旋锁情况下可能进入休眠状态的函数.如果调用这些函数,会浪费CPU资源,因为其他线程需要获取自旋锁需要等待的时间就延长了.

屏障

  • 屏障(barrier)是用户协调多个线程并行工作的同步机制.屏障允许每个线程等待,直到所有的合作线程都达到某一点,然后从改点继续执行.
  • pthread_join 就是一种屏障,允许一个线程等待,直到另一个线程退出.
  • 屏障(barrier)的概念更广,它们允许任意数量的线程等待,知道所有的线程完成处理工作,而线程不需要退出.所有线程到达屏障后可以接着工作.
int pthread_barrier_init(pthread_barrier_t *restrict barrier,
                        const pthread_barrierattr_t *restrict attr,
                        unsigned int count);
int pthread_barrier_destory(pthread_barrier_t *barrier);
// 返回值:成功返回0;否则返回错误编号

初始化呢屏障时,参数 count 制定允许所有的线程继续运行前,必须到达屏障的线程数目.

int pthread_barrier_wait(pthread_barrier_t *barrier);
// 返回值:成功返回0;否则返回错误编号
  • 对于任一线程,pthread_barrier_wait 函数返回了 PTHREAD_BARRIER_SERIAL_THREAD. 剩余的线程看到的返回值为0.这使得一个线程可以作为主线程,它可以工作在其他所有线程已完成的工作结果上.
  • 一旦达到屏障的计数值,而且线程处于非阻塞状态,屏障可以被重用.

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