Linux下多线程编程详解

简介

  • 线程创建
  • 线程属性设置
  • 线程参数传递
  • 线程优先级
  • 线程的数据处理
  • 线程的分离状态
  • 互斥锁
  • 信号量

一 线程创建

废话不多说,先上一个线程版的helloworld。

#include <iostream>
using namespace std;

void *run(void *ptr){
        for(int i=0; i<3; i++) {
          sleep(1);
                cout<<"hello world "<<i<<endl;
        }
        return 0;
}
int main(){
        pthread_t id;
        int ret=0;
        ret=pthread_create(&id,NULL,run,NULL);
        if(ret) {
                cout<<"create thread failed "<<endl;
                return 0;
        }
        pthread_join(id,NULL);
        return 0;
}

运行后

hello world 0
hello world 1
hello world 2

上面的代码很简单,就是启动一个线程,然后先线程里循环打印字段字符串。我们就以这个最简单的例子来开口。

1.1 pthread_create

创建一个线程,函数的声明:

int pthread_create(pthread_t* thread_out, pthread_attr_t const* attr,
                   void* (*start_routine)(void*), void* arg)
  • thread_out 创建线程后的标识符,下面会介绍。
  • attr 设置线程属性。传NULL为默认属性(对大多数程序来说,使用默认属性就够了),当然具体使用下面也会做介绍。
  • start_routine 线程运行函数的起始地址(简单说就是函数指针)。
  • arg 运行函数的参数,这里我们没有使用参数,就直接NULL。

创建成功返回0。若不为0则说明创建线程失败,常见的错误返回代码为EAGAIN和EINVAL。前者表示系统限制创建新的线程,例如线程数目过多了;后者表示第二个参数代表的线程属性值非法。

1.2 pthread_t

定义在 pthreadtypes.h

typedef unsigned long int pthread_t;

线程的标识符。也就是前面创建线程时候传入的参数。当然函数参数做输入的时候,传的是地址。

二 线程属性设置

同样,我们先上示例代码

#include <iostream>
using namespace std;

void *run(void *ptr){
        int value=*(int *)ptr;
        for(int i=0; i<3; i++) {
                sleep(1);
                cout<<"value  "<<value<<endl;
        }
        return 0;
}
int main(){
        int ret=0;
        int value=10;
        pthread_t id;
        pthread_attr_t attr;
        //初始化
        pthread_attr_init(&attr);
        //设置相关属性
        pthread_attr_setscope (&attr,PTHREAD_SCOPE_PROCESS);
        ret=pthread_create(&id,&attr,run,&value);
        if(ret) {
                cout<<"create thread failed "<<endl;
                return 0;
        }
        pthread_join(id,NULL);
        return 0;
}

代码相对于上一节中多了一个属性设置和参数传递。涉及变量pthread_attr_t、pthread_attr_init函数等。整个流程三步走

    1. 定义属性变量pthread_attr_t
    1. 初始化pthread_attr_t
    1. 创建线程时传入。

下面我们具体介绍

2.1 pthread_attr_t

属性对象主要包括是否绑定、是否分离、堆栈地址、堆栈大小、优先级。默认的属性为非绑定、非分离、缺省1M的堆栈、与父进程同样级别的优先级。pthread_attr_t结构的定义,定义在pthread.h

typedef struct
{
    uint32_t flags;
    void * stack_base;
    size_t stack_size;
    size_t guard_size;
    int32_t sched_policy;
    int32_t sched_priority;
} pthread_attr_t;
2.2 属性设置
  • 属性值不能直接设置,须使用相关函数进行操作,初始化的函数为pthread_attr_init,这个函数必须在pthread_create函数之前调用。
  • 调用相关属性设置的方法pthread_attr_set×××即可。

下面列举相关属性操作函数,具体方法含义就不做介绍。

int pthread_attr_init(pthread_attr_t * attr);
int pthread_attr_destroy(pthread_attr_t * attr);

int pthread_attr_setdetachstate(pthread_attr_t * attr, int state);
int pthread_attr_getdetachstate(pthread_attr_t const * attr, int * state);

int pthread_attr_setschedpolicy(pthread_attr_t * attr, int policy);
int pthread_attr_getschedpolicy(pthread_attr_t const * attr, int * policy);

int pthread_attr_setschedparam(pthread_attr_t * attr, struct sched_param const * param);
int pthread_attr_getschedparam(pthread_attr_t const * attr, struct sched_param * param);

int pthread_attr_setstacksize(pthread_attr_t * attr, size_t stack_size);
int pthread_attr_getstacksize(pthread_attr_t const * attr, size_t * stack_size);

int pthread_attr_setstackaddr(pthread_attr_t * attr, void * stackaddr);
int pthread_attr_getstackaddr(pthread_attr_t const * attr, void ** stackaddr);

int pthread_attr_setstack(pthread_attr_t * attr, void * stackaddr, size_t stack_size);
int pthread_attr_getstack(pthread_attr_t const * attr, void ** stackaddr, size_t * stack_size);

int pthread_attr_setguardsize(pthread_attr_t * attr, size_t guard_size);
int pthread_attr_getguardsize(pthread_attr_t const * attr, size_t * guard_size);

int pthread_attr_setscope(pthread_attr_t *attr, int  scope);
int pthread_attr_getscope(pthread_attr_t const *attr);

int pthread_getattr_np(pthread_t thid, pthread_attr_t * attr);

三 线程参数传递

代码还是上一节的示例代码。
参数传递的是指针。我们将value的值传入。

pthread_create(&id,&attr,run,&value);

然后进行指针变量类型转换就可得到值。

int value=*(int *)ptr;

四 线程优先级

先上代码,这里相对于上一节就修改了 main方法,所以只贴出部分代码

int main(){
        int ret=0;
        int value=10;
        pthread_t id;
        pthread_attr_t attr;
        sched_param param;
        //初始化
        pthread_attr_init(&attr);
        //设置相关属性
        pthread_attr_setscope (&attr,PTHREAD_SCOPE_PROCESS);
        //获取线程优先级参数
        pthread_attr_getschedparam(&attr,&param);
        //设置优先级
        param.sched_priority=10;
        pthread_attr_setschedparam(&attr,&param);
        ret=pthread_create(&id,&attr,run,&value);
        if(ret) {
                cout<<"create thread failed "<<endl;
                return 0;
        }
        pthread_join(id,NULL);
        return 0;
}

主要涉及sched_parampthread_attr_setschedparampthread_attr_getschedparam等方法。优先级变量存放在结构sched_param中。用函数pthread_attr_getschedparam和函数pthread_attr_setschedparam进行存放,一般说来,我们总是先取优先级,对取得的值修改后再存放回去。

五 线程的分离状态

线程的分离状态决定一个线程以什么样的方式来终止自己。
在上面的例子中,我们采用了线程的默认属性,即为非分离状态,这种情况下,原有的线程等待创建的线程结束。只有当pthread_join()函数返回时,创建的线程才算终止,才能释放自己占用的系统资源。而分离线程不是这样子的,它没有被其他的线程所等待,自己运行结束了,线程也就终止了,马上释放系统资源。程序员应该根据自己的需要,选择适当的分离状态。设置线程分离状态的函数为pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate)。第二个参数可选为PTHREAD_CREATE_DETACHED(分离线程)和 PTHREAD _CREATE_JOINABLE(非分离线程)。这里要注意的一点是,如果设置一个线程为分离线程,而这个线程运行又非常快,它很可能在pthread_create函数返回之前就终止了,它终止以后就可能将线程号和系统资源移交给其他的线程使用,这样调用pthread_create的线程就得到了错误的线程号。要避免这种情况可以采取一定的同步措施,最简单的方法之一是可以在被创建的线程里调用pthread_cond_timewait函数,让这个线程等待一会儿,留出足够的时间让函数pthread_create返回。设置一段等待时间,是在多线程编程里常用的方法。但是注意不要使用诸如wait()之类的函数,它们是使整个进程睡眠,并不能解决线程同步的问题。

六 线程的数据处理

6.1 线程数据

在单线程的程序里,有两种基本的数据:全局变量和局部变量。但在多线程程序里,还有第三种数据类型:线程数据(TSD: Thread-Specific Data)。它和全局变量很象,在线程内部,各个函数可以象使用全局变量一样调用它,但它对线程外部的其它线程是不可见的。这种数据的必要性是显而易见的。例如我们常见的变量errno,它返回标准的出错信息。它显然不能是一个局部变量,几乎每个函数都应该可以调用它;但它又不能是一个全局变量,否则在A线程里输出的很可能是B线程的出错信息。要实现诸如此类的变量,我们就必须使用线程数据。我们为每个线程数据创建一个键,它和这个键相关联,在各个线程里,都使用这个键来指代线程数据,但在不同的线程里,这个键代表的数据是不同的,在同一个线程里,它代表同样的数据内容。
总结上面的理论:我们要在线程中使用全局变量,但是这个全局变量在各个线程中是独立的。
先上代码:

#include <iostream>
using namespace std;

//用户访问和获取线程变量。所有的线程都可以访问
pthread_key_t key;
void *run(void *ptr){
        int value=*(int *)ptr;
        int temp=1;
        //将temp的地址赋给key。
        pthread_setspecific (key, &temp);
        for(int i=0; i<3; i++) {
                //根据key获取对应的值
                int v=*(int *)pthread_getspecific(key);
                usleep(1000*100);
                cout<<"run key value>> "<<v<<endl<<flush;
        }
        return 0;
}
void *run2(void *ptr){
        int temp=2;
        pthread_setspecific (key, &temp);
        for(int i=0; i<3; i++) {
                int v=*(int *)pthread_getspecific(key);
                usleep(1000*150);
                cout<<"run2 key value>> "<<v<<endl<<flush;
        }
        return 0;
}

int main(){
        //创建key
        pthread_key_create(&key,NULL);
        int ret=0;
        int value=10;
        pthread_t id;
        pthread_attr_t attr;
        sched_param param;
        //初始化
        pthread_attr_init(&attr);
        //设置相关属性
        pthread_attr_setscope (&attr,PTHREAD_SCOPE_PROCESS);
        //获取线程优先级参数
        pthread_attr_getschedparam(&attr,&param);
        //设置优先级
        param.sched_priority=10;
        pthread_attr_setschedparam(&attr,&param);
        ret=pthread_create(&id,&attr,run,&value);
        if(ret) {
                cout<<"create thread failed "<<endl;
                return 0;
        }
        pthread_t id2;
        ret=pthread_create(&id2,NULL,run2,NULL);
        if(ret) {
                cout<<"create thread 2 failed "<<endl;
                return 0;
        }
        pthread_join(id,NULL);
        pthread_join(id2,NULL);
        pthread_key_delete(key);
        return 0;
}

相关的函数和结构有pthread.hpthread_key_createpthread_key_deletepthread_key_tpthread_setspecificpthread_getspecific

  • pthread_key_create
    创建函数声明:
int pthread_key_create(pthread_key_t *key, void (*destructor_function)(void *))

函数 pthread_key_create() 用来创建线程私有数据。该函数从 TSD 池中分配一项,将其地址值赋给 key 供以后访问使用。

  • pthread_key_t key
    指向一个键值的指针 pthread_key_t的定义为typedef int pthread_key_t;不论哪个线程调用了 pthread_key_create(),所创建的 key 都是所有线程可以访问的,但各个线程可以根据自己的需要往 key 中填入不同的值,相当于提供了一个同名而不同值的全局变量(这个全局变量相对于拥有这个变量的线程来说)。
  • destructor_function
    这是一个销毁函数,它是可选的,可以为 NULL,为 NULL 时,则系统调用默认的销毁函数进行相关的数据注销。如果不为空,则在线程退出时(调用 pthread_exit() 函数)时将以 key 锁关联的数据作为参数调用它,以释放分配的缓冲区,或是关闭文件流等。
  • pthread_setspecific/pthread_getspecific
    设置和获取线程变量的值。

七 互斥锁

互斥锁用来保证一段时间内只有一个线程在执行一段代码。必要性显而易见:假设各个线程向同一个文件顺序写入数据,最后得到的结果一定是灾难性的。
先上代码,这里我们做了一个读写模型:

#include <iostream>
using namespace std;
char buffer;
int buffer_has_item=0;
pthread_mutex_t mutex;
void writerFunc(){
        while(1) {
                /* 锁定互斥锁*/
                pthread_mutex_lock (&mutex);
                buffer_has_item++;
                cout<<"write "<<buffer_has_item<<endl;
                /* 打开互斥锁*/
                pthread_mutex_unlock(&mutex);
                usleep(1000*200);
        }
}
void *readerFunc(void *ptr){
        while(1) {
                pthread_mutex_lock(&mutex);
                if(buffer_has_item>0) {
                        cout<<"read >>>> "<<buffer_has_item<<endl;
                        buffer_has_item--;
                }
                pthread_mutex_unlock(&mutex);
                usleep(1000*500);
        }
}
int main(){
        pthread_t id;
        pthread_mutex_init (&mutex,NULL);
        pthread_create(&id, NULL,readerFunc, NULL);
        writerFunc();
        return 0;
}

结果:

[root@localhost threadDemo]# ./second 
write 1
read >>>> 1
write 1
write 2
read >>>> 2
write 2
write 3
read >>>> 3
7.1 核心流程
  • 定义一个锁(pthread_mutex_t)
  • 初始化锁
  • 使用pthread_mutex_lock/pthread_mutex_unlock进行锁定和解锁。

先看下 互斥锁pthread_mutex_t的定义

typedef struct
{
    int volatile value;
} pthread_mutex_t;

使用前需要初始化互斥锁pthread_mutex_init

7.2 锁定和解锁

pthread_mutex_lock 声明开始用互斥锁上锁,此后的代码直至调用pthread_mutex_unlock为止,均被上锁,即同一时间只能被一个线程调用执行。当一个线程执行到pthread_mutex_lock处时,如果该锁此时被另一个线程使用,那此线程被阻塞,即程序将等待到另一个线程释放此互斥锁

7.3 互斥锁其他相关方法
  • pthread_mutexattr_setpshared设置属性pshared。可以取:
    • PTHREAD_PROCESS_SHARED 不同进程中的线程同步
    • PTHREAD_PROCESS_PRIVATE 同进程中的不同线程同步
  • pthread_mutexattr_settype 设置互斥锁类型
    其他:
int pthread_mutexattr_init(pthread_mutexattr_t *attr);
int pthread_mutexattr_destroy(pthread_mutexattr_t *attr);
int pthread_mutexattr_gettype(const pthread_mutexattr_t *attr, int *type);
int pthread_mutexattr_settype(pthread_mutexattr_t *attr, int type);
int pthread_mutexattr_setpshared(pthread_mutexattr_t *attr, int  pshared);
int pthread_mutexattr_getpshared(pthread_mutexattr_t *attr, int *pshared);

int pthread_mutex_init(pthread_mutex_t *mutex,
                       const pthread_mutexattr_t *attr);
int pthread_mutex_destroy(pthread_mutex_t *mutex);
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
#if 0 /* MISSING FROM BIONIC */
int pthread_mutex_timedlock(pthread_mutex_t *mutex, struct timespec*  ts);
#endif /* MISSING */

八 条件变量

前一节中我们讲述了如何使用互斥锁来实现线程间数据的共享和通信,互斥锁一个明显的缺点是它只有两种状态:锁定和非锁定。对于消费者,它根本不知道什么时候生产者已经生产了数据,只能通过轮询来检测,这就有两个缺陷:

    1. 轮询会占用CPU资源
    1. 轮询的时间不好控制,可能导致消费者执行不及时。

为了解决上面这个问题,我们这样考虑。费线程在阻塞之前要先解锁(个人想法:消费线程已经获得了要访问资源的锁,但是,即使我获得了资源的锁,但是由于条件暂时还不满足,我无法用这个资源,所以我想暂时让出这把锁,让之里的资源暂时为别人所用,所以在挂起前,我需要解锁),同时还要将自己的标识符放入一个地方,以便生产线程通过这个标识符来激活自己。那新问题又来了,由于线程之间是并发/并行的。消费线程可能刚完成解锁的操作,就被生产线程获取到了并开始执行,这时,因为消费线程还未挂起自己,来不及将自己的标识符保存在某个位置,所以生产线程不认为有正在等待的线程(生产线程想告诉消费线程的唯一方式就是认消费线程的标识符)。这时,切换到消费线程后,消费线程将永远的等待下去,虽然队列中有产品,但生产线程也不会告诉消费线程。而生产线程因为队列中有产品可能也一直的等待下去,形成了死锁。

这里死锁的原因很明确,就是因为消费线程在阻塞之前要先解锁解、保存线程标识符、挂起这一系列操作不是原子操作。想要让这一些列的操作成为原子操作,就得引入条件变量,所以不难想到使用条件变量的时候必须要“伴随”一个互斥量。

条件变量是与互斥量相关联的一种用于多线程之间关于共享数据状态改变的通信机制。它将解锁和挂起封装成为原子操作。等待一个条件变量时,会解开与该条件变量相关的锁,因此,使用条件变量等待的前提之一就是保证互斥量加锁。线程醒来之后,该互斥量会被自动加锁,所以,在完成相关操作之后需要解锁。

用条件变量配合互斥量实现,条件变量与互斥量结合,使得在条件不满足的情况下,能够释放对缓冲区的占用,使得他人能够访问缓冲区。当我添加满足时,我又可以及时的加锁之后独占资源的完成我自己的工作。

我们先上测试代码:

#include <iostream>
using namespace std;
int buffer_has_item=0;
pthread_mutex_t mutex;
pthread_cond_t count_nonzero;
void writerFunc(){
        while(1) {
                pthread_mutex_lock (&mutex);
                buffer_has_item+=2;
                cout<<"write "<<buffer_has_item<<endl;
                pthread_mutex_unlock(&mutex);
                //激活阻塞的读线程
                pthread_cond_signal(&count_nonzero);
                usleep(1000*200);
        }
}
void *readerFunc(void *ptr){
        while(1) {
                pthread_mutex_lock(&mutex);
                if(buffer_has_item<=0) {
                        //暂时解锁,把资源让出,等待写线程写入。
                        //被激活后会自动加锁
                        pthread_cond_wait( &count_nonzero, &mutex);
                }
                cout<<"read >>>> "<<buffer_has_item<<endl;
                buffer_has_item--;
                pthread_mutex_unlock(&mutex);
        }
}
int main(){
        pthread_t id;
        pthread_mutex_init (&mutex,NULL);
        pthread_create(&id, NULL,readerFunc, NULL);
        writerFunc();
        return 0;
}

主要涉及内容有pthread_cond_tpthread_cond_signalpthread_cond_wait

pthread_condattr_t的定义为

typedef long pthread_condattr_t;

用来定义条件变量。

  • pthread_cond_wait
    线程解开mutex指向的锁并被条件变量cond阻塞。线程可以被函数pthread_cond_signal和函数pthread_cond_broadcast唤醒。,但是要注意的是,条件变量只是起阻塞和唤醒线程的作用,具体的判断条件还需用户给出,例如一个变量是否<=0等等

  • pthread_cond_signal
    用来释放被阻塞在条件变量cond上的一个线程。多个线程阻塞在此条件变量上时,哪一个线程被唤醒是由线程的调度策略所决定的。

其他相关函数:

int pthread_condattr_init(pthread_condattr_t *attr);
int pthread_condattr_getpshared(pthread_condattr_t *attr, int *pshared);
int pthread_condattr_setpshared(pthread_condattr_t* attr, int pshared);
int pthread_condattr_destroy(pthread_condattr_t *attr);

int pthread_cond_init(pthread_cond_t *cond,
                      const pthread_condattr_t *attr);
int pthread_cond_destroy(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);
int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
int pthread_cond_timedwait(pthread_cond_t *cond,
                           pthread_mutex_t * mutex,
                           const struct timespec *abstime);

/* BIONIC: same as pthread_cond_timedwait, except the 'abstime' given refers
 *         to the CLOCK_MONOTONIC clock instead, to avoid any problems when
 *         the wall-clock time is changed brutally
 */
int pthread_cond_timedwait_monotonic_np(pthread_cond_t         *cond,
                                        pthread_mutex_t        *mutex,
                                        const struct timespec  *abstime);

/* BIONIC: DEPRECATED. same as pthread_cond_timedwait_monotonic_np()
 * unfortunately pthread_cond_timedwait_monotonic has shipped already
 */
int pthread_cond_timedwait_monotonic(pthread_cond_t         *cond,
                                     pthread_mutex_t        *mutex,
                                     const struct timespec  *abstime);

#define HAVE_PTHREAD_COND_TIMEDWAIT_MONOTONIC 1

/* BIONIC: same as pthread_cond_timedwait, except the 'reltime' given refers
 *         is relative to the current time.
 */
int pthread_cond_timedwait_relative_np(pthread_cond_t         *cond,
                                     pthread_mutex_t        *mutex,
                                     const struct timespec  *reltime);

#define HAVE_PTHREAD_COND_TIMEDWAIT_RELATIVE 1



int pthread_cond_timeout_np(pthread_cond_t *cond,
                            pthread_mutex_t * mutex,
                            unsigned msecs);

九 信号量

线程的信号量与进程间通信中使用的信号量的概念是一样,它是一种特殊的变量,本质上是一个非负的整数计数器,它被用来控制对公共资源的访问。它可以被增加或减少,但对其的关键访问被保证是原子操作。如果一个程序中有多个线程试图改变一个信号量的值,系统将保证所有的操作都将依次进行。
信调用函数sem_post()增加信号量。只有当信号量值大于0时,才能使用公共资源,使用后,函数sem_wait()减少信号量。
老规矩,先上代码,跑起来再说:

#include <iostream>
#include <semaphore.h>
#include <string.h>
#include <stdio.h>
using namespace std;
//信号量
sem_t sem;
void * run(void *ptr){
        char *buf=(char *)ptr;
        while(strcmp("exit\n",buf)!=0) {
                //新号量-1
                sem_wait(&sem);
                cout<<"thread output>> "<<buf<<endl<<flush;
        }
        return 0;
}

int main(){
        char buf[100]={0};
        int ret=0;
        pthread_t id;
        //初始化信号量数量为2。默认类型,非0位进程间共享
        ret=sem_init(&sem, 0, 2);
        if(ret) {
                cout<<"sem_init failed"<<endl;
        }
        pthread_create(&id,NULL,run,buf);
        //循环从标准输入读(fgets会将\n也读入)
        while(fgets(buf,sizeof(buf),stdin)) {
                if(strcmp("exit\n",buf)==0) {
                        break;
                }
                //信号量+1
                sem_post(&sem);
        }
        //清理信号量
        sem_destroy(&sem);
        return 0;
}

函数sem_trywait()和函数pthread_ mutex_trylock()起同样的作用,它是函数sem_wait()的非阻塞版本。下面我们逐个介绍和信号量有关的一些函数,它们都在头文件/usr/include/semaphore.h中定义。

#include <sys/cdefs.h>

__BEGIN_DECLS

typedef struct {
    volatile unsigned int  count;
} sem_t;

#define  SEM_FAILED  NULL

extern int sem_init(sem_t *sem, int pshared, unsigned int value);

extern int    sem_close(sem_t *);
extern int    sem_destroy(sem_t *);
extern int    sem_getvalue(sem_t *, int *);
extern int    sem_init(sem_t *, int, unsigned int);
extern sem_t *sem_open(const char *, int, ...);
extern int    sem_post(sem_t *);
extern int    sem_trywait(sem_t *);
extern int    sem_unlink(const char *);
extern int    sem_wait(sem_t *);

struct timespec;
extern int    sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);

结果:

[root@localhost threadDemo]# ./fourth 
thread output>> 
thread output>> 
hello
thread output>> hello

come on baby
thread output>> come on baby

exit

这里需要引入新的头文件semaphore.h

  • sem_init
    初始化信号量。该函数初始化由sem指向的信号对象,设置它的共享选项,并给它一个初始的整数值。pshared控制信号量的类型,如果其值为0,就表示这个信号量是当前进程的局部信号量,否则信号量就可以在多个进程之间共享,value为sem的初始值。调用成功时返回0,失败返回-1.
  • sem_post ( sem_t *sem )
    该函数用于以原子操作的方式将信号量的值加1。当有线程阻塞在这个信号量上时,调用这个函数会使其中的一个线程不在阻塞,选择机制同样是由线程的调度策略决定的。
  • sem_wait( sem_t *sem )
    被用来阻塞当前线程直到信号量sem的值大于0,解除阻塞后将sem的值减1,表明公共资源经使用后减少。
  • sem_destroy
    该函数用于对用完的信号量的清理

semaphore.h头文件

#ifndef _SEMAPHORE_H
#define _SEMAPHORE_H    1

#include <features.h>
#include <sys/types.h>
#ifdef __USE_XOPEN2K
# define __need_timespec
# include <time.h>
#endif

/* Get the definition for sem_t.  */
#include <bits/semaphore.h>


__BEGIN_DECLS

/* Initialize semaphore object SEM to VALUE.  If PSHARED then share it
   with other processes.  */
extern int sem_init (sem_t *__sem, int __pshared, unsigned int __value)
     __THROW;
/* Free resources associated with semaphore object SEM.  */
extern int sem_destroy (sem_t *__sem) __THROW;

/* Open a named semaphore NAME with open flags OFLAG.  */
extern sem_t *sem_open (__const char *__name, int __oflag, ...) __THROW;

/* Close descriptor for named semaphore SEM.  */
extern int sem_close (sem_t *__sem) __THROW;

/* Remove named semaphore NAME.  */
extern int sem_unlink (__const char *__name) __THROW;

/* Wait for SEM being posted.

   This function is a cancellation point and therefore not marked with
   __THROW.  */
extern int sem_wait (sem_t *__sem);

#ifdef __USE_XOPEN2K
/* Similar to `sem_wait' but wait only until ABSTIME.

   This function is a cancellation point and therefore not marked with
   __THROW.  */
extern int sem_timedwait (sem_t *__restrict __sem,
              __const struct timespec *__restrict __abstime);
#endif

/* Test whether SEM is posted.  */
extern int sem_trywait (sem_t *__sem) __THROW;

/* Post SEM.  */
extern int sem_post (sem_t *__sem) __THROW;

/* Get current value of SEM and store it in *SVAL.  */
extern int sem_getvalue (sem_t *__restrict __sem, int *__restrict __sval)
     __THROW;


__END_DECLS

#endif  /* semaphore.h */

参考博客:

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,233评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,013评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,030评论 0 241
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,827评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,221评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,542评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,814评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,513评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,225评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,497评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,998评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,342评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,986评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,812评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,560评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,461评论 2 266

推荐阅读更多精彩内容

  • 转自:Youtherhttps://www.cnblogs.com/youtherhome/archive/201...
    njukay阅读 1,593评论 0 52
  • 线程基础 线程是进程的一个执行单元,执行一段程序片段,线程共享全局变量;线程的查看可以使用命令或者文件来进行查看;...
    秋风弄影阅读 720评论 0 0
  • linux线程同步 信号灯:与互斥锁和条件变量的主要不同在于"灯"的概念,灯亮则意味着资源可用,灯灭则意味着不可用...
    鲍陈飞阅读 657评论 0 2
  • 摘要 线程概念,线程与进程的区别与联系学会线程控制,线程创建,线程终止,线程等待了解线程分离与线程安全学会线程同步...
    狼之足迹阅读 443评论 2 3
  • 一、线程的创建和调度 1.线程是程序执行的某一条指令流的映像。 为了进一步减少处理机制的空转时间,支持多处理器及减...
    穹蓝奥义阅读 1,094评论 2 5