嵌入式 Linux进程间通信(十二)——多线程同步

嵌入式 Linux进程间通信(十二)——多线程同步

多线程编程中有三种线程同步机制:互斥锁、信号量、条件量。本文将使用生产者消费者问题编程实践三种线程同步方式。

生产者、消费者问题:生产者线程生产物品,然后将物品放置在一个空缓冲区中供消费者线程消费。消费者线程从缓冲区中获得物品,然后释放缓冲区。当生产者线程生产物品时,如果没有空缓冲区可用,那么生产者线程必须等待消费者线程释放出一个空缓冲区。当消费者线程消费物品时,如果没有满的缓冲区,那么消费者线程将被阻塞,直到新的物品被生产出来。

一、互斥锁

互斥锁用来保证同一时间内只有一个线程在执行某段代码(临界区)。互斥锁可看作某种意义上的全局变量。在同一时刻只能有一个线程掌握某个互斥锁,拥有上锁状态的线程能够对共享资源进行操作。若其他线程希望上锁一个已经被上锁的互斥锁,则该线程就会挂起,直到上锁的线程释放掉互斥锁为止。互斥锁保证让每个线程对共享资源按顺序进行原子操作。

互斥锁主要包括下面的基本函数:

  互斥锁初始化:pthread_mutex_init()

  互斥锁上锁:pthread_mutex_lock()

  互斥锁判断上锁:pthread_mutex_trylock()

  互斥锁解锁:pthread_mutex_unlock()

  消除互斥锁:pthread_mutex_destroy()

1、初始化互斥锁

静态分配:pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
    动态分配:int pthread_mutex_init(pthread_mutex_t *mp, const pthread_mutexattr_t *mattr);

mp是互斥锁地址

mattr是属性,通常默认 null初始化互斥锁之前,必须将其所在的内存清零。如果互斥锁已初始化,则它会处于未锁定状态。互斥锁可以位于进程之间共享的内存中或者某个进程的专用内存中。

2、锁定互斥锁

int pthread_mutex_lock(pthread_mutex_t *mutex);

当pthread_mutex_lock() 返回时,该互斥锁已被锁定。调用线程是该互斥锁的属主。如果该互斥锁已被另一个线程锁定和拥有,则调用线程将阻塞,直到该互斥锁变为可用为止。
    如果互斥锁类型为 PTHREAD_MUTEX_NORMAL,则不提供死锁检测。尝试重新锁定互斥锁会导致死锁。如果某个线程尝试解除锁定的互斥锁不是由该线程锁定或未锁定,则将产生不确定的行为。
    如果互斥锁类型为 PTHREAD_MUTEX_ERRORCHECK,则会提供错误检查。如果某个线程尝试重新锁定的互斥锁已经由该线程锁定,则将返回错误。如果某个线程尝试解除锁定的互斥锁不是由该线程锁定或者未锁定,则将返回错误。
    如果互斥锁类型为 PTHREAD_MUTEX_RECURSIVE,则该互斥锁会保留锁定计数这一概念。线程首次成功获取互斥锁时,锁定计数会设置为 1。线程每重新锁定该互斥锁一次,锁定计数就增加 1。线程每解除锁定该互斥锁一次,锁定计数就减小 1。 锁定计数达到 0 时,该互斥锁即可供其他线程获取。如果某个线程尝试解除锁定的互斥锁不是由该线程锁定或者未锁定,则将返回错误。
    如果互斥锁类型是 PTHREAD_MUTEX_DEFAULT,则尝试以递归方式锁定该互斥锁将产生不确定的行为。对于不是由调用线程锁定的互斥锁,如果尝试解除对它的锁定,则会产生不确定的行为。如果尝试解除锁定尚未锁定的互斥锁,则会产生不确定的行为。
    返回值:pthread_mutex_lock() 在成功完成之后会返回零。其他任何返回值都表示出现了错误。如果出现以下任一情况,该函数将失败并返回对应的值。
        EAGAIN:由于已超出了互斥锁递归锁定的最大次数,因此无法获取该互斥锁。
        EDEADLK:当前线程已经拥有互斥锁。

3、解除锁定互斥锁

int pthread_mutex_unlock(pthread_mutex_t *mutex);

函数说明:pthread_mutex_unlock() 可释放 mutex 引用的互斥锁对象。互斥锁的释放方式取决于互斥锁的类型属性。如果调用 pthread_mutex_unlock() 时有多个线程被 mutex 对象阻塞,则互斥锁变为可用时调度策略可确定获取该互斥锁的线程。对于 PTHREAD_MUTEX_RECURSIVE 类型的互斥锁,当计数达到零并且调用线程不再对该互斥锁进行任何锁定时,该互斥锁将变为可用。
    返回值:pthread_mutex_unlock() 在成功完成之后会返回零。其他任何返回值都表示出现了错误。如果出现以下情况,该函数将失败并返回对应的值。
EPERM :当前线程不拥有互斥锁。

4、销毁互斥锁

int pthread_mutex_destroy(pthread_mutex_t *mp);

注意,没有释放用来存储互斥锁的空间。
    返回值:pthread_mutex_destroy() 在成功完成之后会返回零。其他任何返回值都表示出现了错误。如果出现以下任一情况,该函数将失败并返回对应的值。
        EINVAL: mp 指定的值不会引用已初始化的互斥锁对象。

5、程序实例

本实例使用互斥锁实现生产者、消费者的经典例子,生产者、消费者公用一个缓冲区,缓冲区存放一条消息。

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <sys/time.h>
 
static char buff[50];
int msg_flag=0;
pthread_mutex_t mutex;
 
void consumeItem(char *buff)
{
    printf("This is a consumer item\n");
}
void produceItem(char *buff)
{
    printf("This is a produce item\n");
}
void *consumer(void *param)
{
    while (1)
    {
        pthread_mutex_lock(&mutex);
        if (msg_flag > 0)
        {
            msg_flag--;
            consumeItem(buff);
        }
        pthread_mutex_unlock(&mutex);
        sleep(1);
    }
    return NULL;
}
 
void *producer(void *param)
{
    while (1)
    {
        pthread_mutex_lock(&mutex);
        if (msg_flag ==0 )
        {
            msg_flag++;
            produceItem(buff);
        }
        pthread_mutex_unlock(&mutex);
        sleep(1);
    }
    return NULL;
}
 
int main()
{
    pthread_t tid_c, tid_p;
    void *retval;
    pthread_mutex_init(&mutex, NULL);
    pthread_create(&tid_p, NULL, producer, NULL);
    pthread_create(&tid_c, NULL, consumer, NULL);
 
    pthread_join(tid_p, &retval);
    pthread_join(tid_c, &retval);
 
    return 0;
}

二、信号量

与进程一样,线程也可以使用信号量来通信。线程使用信号量同步线程的步骤如下:

1、信号量初始化

int sem_init (sem_t *sem , int pshared, unsigned int value);
        对sem指定的信号量进行初始化,设置好共享选项(linux只支持为0,即表示它是当前进程的局部信号量),然后给它一个初始值VALUE。

2、等待信号量

int sem_wait(sem_t *sem);

给信号量减1,然后等待直到信号量的值大于0。

3、释放信号量

int sem_post(sem_t *sem);

信号量值加1。并通知其他等待线程。

4、销毁信号量

int sem_destroy(sem_t *sem);

5、程序实例

本实例使用条件变量实现生产者、消费者的经典例子,生产者、消费者共用一个缓冲区,缓冲区存放多条消息。使用信号量使用如下:

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h>
 
static char buff[50];
pthread_mutex_t mutex;
pthread_mutex_t cond_mutex;
pthread_cond_t cond;
sem_t msg_cnt;      //缓冲区消息数
sem_t idle_cnt;   //缓冲区空闲数
 
void consumeItem(char *buff)
{
    printf("This is a consumer item\n");
}
void produceItem(char *buff)
{
    printf("This is a produce item\n");
}
void *consumer(void *param)
{
    while (1)
    {
        sem_wait(&msg_cnt);
        pthread_mutex_lock(&mutex);
        consumeItem(buff);
        pthread_mutex_unlock(&mutex);
        sem_post(&idle_cnt);
    }
    return NULL;
}
 
void *producer(void *param)
{
    while (1)
    {
        sem_wait(&idle_cnt);
        pthread_mutex_lock(&mutex);
        produceItem(buff);        
        pthread_mutex_unlock(&mutex);
        sem_post(&msg_cnt);
        
    }
    return NULL;
}
 
int main()
{
    pthread_t tid_c, tid_p;
    void *retval;
    pthread_mutex_init(&mutex, NULL);
    pthread_mutex_init(&cond_mutex, NULL);
    
    pthread_cond_t cond;
    pthread_cond_init(&cond, NULL);
 
    sem_init(&msg_cnt, 0, 0);        //初始缓冲区没有消息
    sem_init(&idle_cnt, 0, 10);  //初始缓冲区能放10条消息
    
    pthread_create(&tid_p, NULL, producer, NULL);
    pthread_create(&tid_c, NULL, consumer, NULL);
    
        
    pthread_join(tid_p, &retval);
    pthread_join(tid_c, &retval);
            
    return 0;
}

、条件变量(cond)

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

条件变量用来阻塞线程等待某个事件的发生,并且当等待的事件发生时,阻塞线程会被通知。互斥锁的缺点是只有两种状态:锁定和非锁定。而条件变量通过允许线程阻塞和等待另一个线程发送信号的方法弥补了互斥锁的不足,常和互斥锁一起使用。使用时,条件变量被用来阻塞一个线程,当条件不满足时,线程往往解开相应的互斥锁并等待条件发生变化。一旦其它的某个线程改变了条件变量,它将通知相应的条件变量唤醒一个或多个正被此条件变量阻塞的线程。这些线程将重新锁定互斥锁并重新测试条件是否满足。一般说来,条件变量被用来进行线承间的同步

1、初始化条件变量

静态初始化:pthread_cond_t cond = PTHREAD_COND_INITIALIER;
    动态初始化:ead_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr);

2等待条件成立

释放锁,同时阻塞等待条件变量为真才行。timewait()设置等待时间,仍未signal,返回ETIMEOUT(加锁保证只有一个线程wait)
    int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
    int thread_cond_timewait(pthread_cond_t *cond,pthread_mutex *mutex,const timespec *abstime);

3、激活条件变量

int pthread_cond_signal(pthread_cond_t *cond);
    int pthread_cond_broadcast(pthread_cond_t *cond); 解除所有线程的阻塞

4清除条件变量

无线程等待,否则返回EBUSY

int pthread_cond_destroy(pthread_cond_t *cond);

5、程序实例

本实例使用条件变量实现生产者、消费者的经典例子,生产者、消费者公用一个缓冲区,缓冲区存放多条消息,有多个消费者。如果都使用互斥锁,那么多个消费者线程都要不断的去查看缓冲区是否有消息,有就取走。如果我们用条件变量,多个消费者线程都可以放心的“睡觉”,缓冲区有消息,消费者会收到通知,进而收取消息就行。

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <sys/time.h>
 
static char buff[50];
pthread_mutex_t mutex;
pthread_mutex_t cond_mutex;
pthread_cond_t cond;
 
void consumeItem(char *buff)
{
    printf("This is a consumer item\n");
}
void produceItem(char *buff)
{
    printf("This is a produce item\n");
}
void *consumer(void *param)
{
    int t = *(int *)param;
    while (1)
    {
        pthread_cond_wait(&cond, &cond_mutex);
        pthread_mutex_lock(&mutex);
        printf("consumer thread %d: ", t);
        consumeItem(buff);
        pthread_mutex_unlock(&mutex);
    }
    return NULL;
}
 
void *producer(void *param)
{
    while (1)
    {
        pthread_mutex_lock(&mutex);
        produceItem(buff);        
        pthread_mutex_unlock(&mutex);
        pthread_cond_signal(&cond);
        sleep(1);
    }
    return NULL;
}
 
int main(int argc, char **argv)
{
    pthread_t tid_c1, tid_p, tid_c2, tid_c3;
    void *retval;
    pthread_mutex_init(&mutex, NULL);
    pthread_mutex_init(&cond_mutex, NULL);
    
    pthread_cond_t cond;
    pthread_cond_init(&cond, NULL);
    
    
    int p[3] = {1, 2, 3}; //线程编号
    pthread_create(&tid_p, NULL, producer, NULL);
    pthread_create(&tid_c1, NULL, consumer, &p[0]);
    pthread_create(&tid_c2, NULL, consumer, &p[1]);
    pthread_create(&tid_c3, NULL, consumer, &p[2]);
 
    
    pthread_join(tid_p, &retval);
    pthread_join(tid_c1, &retval);
    pthread_join(tid_c2, &retval);
    pthread_join(tid_c3, &retval);
        
    return 0;
}
时间: 2024-10-12 04:43:59

嵌入式 Linux进程间通信(十二)——多线程同步的相关文章

嵌入式 Linux进程间通信(二)——exec族函数

嵌入式 Linux进程间通信(二)--exec族函数 exec函数族的作用是根据指定的文件名找到可执行文件,并用它来取代调用进程的内容,换句话说,就是在调用进程内部执行一个可执行文件.这里的可执行文件既可以是二进制文件,也可以是任何Linux下可执行的脚本文件. exec族函数包含如下函数: #include <unistd.h> extern char **environ; int execl(const char *path, const char *arg, ...); int exec

嵌入式 Linux进程间通信(十一)——多线程简介

嵌入式 Linux进程间通信(十一)--多线程简介 一.线程简介 线程有四种:内核线程.轻量级进程.用户线程.加强版用户线程 1.内核线程 内核线程就是内核的分身,一个分身可以处理一件特定事情.内核线程的使用是廉价的,唯一使用的资源就是内核栈和上下文切换时保存寄存器的空间.支持多线程的内核叫做多线程内核(Multi-Threads kernel ). 2.轻量级进程LWP 轻量级进程(LWP)是一种由内核支持的用户线程,是基于内核线程的高级抽象,只有先支持内核线程,才能有轻量级进程LWP.每一个

Linux进程间通信与线程间同步详解(全面详细)

引用:http://community.csdn.net/Expert/TopicView3.asp?id=4374496linux下进程间通信的几种主要手段简介: 1. 管道(Pipe)及有名管道(named pipe):管道可用于具有亲缘关系进程间的通信,有名管道克服了管道没有名字的限制,因此,除具有管道所具有的功能外,它还允许无亲缘关系进程间的通信:   2. 信号(Signal):信号是比较复杂的通信方式,用于通知接受进程有某种事件发生,除了用于进程间通信外,进程还可以发送信号给进程本身

嵌入式 Linux进程间通信(五)——进程间通信简介

嵌入式 Linux进程间通信(五)--进程间通信简介 一.进程间通信简介 Linux的进程通信方式基本上是从Unix平台上的进程通信方式继承而来的.在Unix发展过程中,贝尔实验室和BSD(加州大学伯克利分校的伯克利软件发布中心)是Unix发展的主要贡献者,但两者在进程间通信方面的侧重点有所不同.贝尔实验室对Unix早期的进程间通信方式进行了系统的改进和扩充,形成了 "system V IPC",通信进程局限在本地计算机内:BSD则跳过了进程通信局限在本地计算机的限制,形成了可以在计算

嵌入式 Linux进程间通信(七)——消息队列

嵌入式 Linux进程间通信(七)--消息队列 一.消息队列 消息队列用于同一台机器上的进程间通信,是一个在系统内核中用来保存消息的队列,在系统内核中是以消息链表的形式出现.消息链表中节点的结构用msg声明.消息队列是一种从一个进程向另一个进程发送数据块的方法. 每个数据块都被认为含有一个类型,接收进程可以独立地接收含有不同类型的数据结构.消息队列可以避免命名管道的同步和阻塞问题,但是每个数据块都有一个最大长度的限制. 1.msgget函数 #include <sys/types.h> #in

嵌入式 Linux进程间通信(八)——共享内存

嵌入式 Linux进程间通信(八)--共享内存 一.共享内存 共享内存允许两个或更多进程共享给定的内存区,数据不需要在不同进程间进行复制,是最快的进程间通信方式.使用共享内存唯一需要注意的是多个进程之间对给定存储区的同步访问,但共享内存本身没有提供同步机制,通常使用信号量来实现对共享内存访问的同步. 共享内存编程流程:创建共享内存.映射共享内存.使用共享内存.撤销映射操作.删除共享内存 1.创建共享内存 #include <sys/ipc.h> #include <sys/shm.h&g

嵌入式 Linux进程间通信(一)——进程

嵌入式 Linux进程间通信(一)--进程 进程是程序的实例化,是运行中的程序.程序在编译时用链接器,运行时用加载器.进程运行在虚拟地址空间,操作系统中每个进程在独立的地址空间中运行,每个进程的而逻辑地址空间均为4GB(32位系统),0-1G位OS,1G-4G为应用. 每个进程都有一个在操作系统内唯一的进程号,进程号的获取函数有: #include <sys/types.h> #include <unistd.h> pid_t getpid(void);//返回当前运行进程的而进程

嵌入式 Linux进程间通信(六)——管道

嵌入式 Linux进程间通信(六)--管道 一.管道 管道是单向的.先进先出的.无结构的.固定大小的字节流.写进程在管道的尾端写入数据,读进程在管道的首端读出数据.数据读出后将从管道中移走,其它读进程都不能再读到这些数据.管道提供了简单的流控制机制.进程试图读空管道时,在有数据写入管道前,进程将一直阻塞:管道已经满时,进程再试图写管道,在其它进程从管道中移走数据之前,写进程将一直阻塞.管道存在于系统内核之中,管道只能用于具有亲缘关系进程间的通信.管道是半双工的,数据只能向一个方向流动:需要双方通

嵌入式 Linux进程间通信(三)——守护进程

嵌入式 Linux进程间通信(三)--守护进程 一.守护进程简介 1.守护进程简介 守护进程(Daemon)是运行在后台.独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件的一种特殊进程.守护进程常常在系统引导装入时启动,在系统关闭时终止.Linux的大多数服务器就是用守护进程实现的.比如,Internet服务器inetd,Web服务器httpd等.同时,守护进程完成许多系统任务.比如,作业规划进程crond等.守护进程的创建本身并不复杂,复杂的是各种版本的Unix的实现机制不尽相同