Linux互斥与同步应用(二):posix线程同步

【版权声明:尊重原创,转载请保留出处:blog.csdn.net/shallnet 或 .../gentleliu,文章仅供学习交流,请勿用于商业用途】

上一节说到线程的互斥锁,互斥锁适合防止访问某个共享变量,这一节我们来看看两个线程如何实现同步。互斥锁也可以实现线程同步,当该值满足某种条件时当前线程继续执行,否则继续轮询,不过这样相当浪费cpu时间。我们需要的是让某个线程进入睡眠,当满足该线程执行条件时,另外线程主动通知它,这就是这一节我们要说的条件变量,它常和互斥锁一起使用。

如同信号量,线程可以对一个条件变量执行等待操作。如果如果线程 A 正在等待一个条件变量,它会被阻塞直到另外一个线程B,向同一个条件变量发送信号以改变其状态。不同于信号量,条件变量没有计数值,也不占据内存空间,线程
A 必须在 B 发送信号之前开始等待。如果 B 在 A 执行等待操作之前发送了信号,这个信号就丢失了,同时 A会一直阻塞直到其它线程再次发送信号到这个条件变量。

条件变量将允许你实现这样的目的:在一种情况下令线程继续运行,而相反情况下令线程阻塞。只要每个可能涉及到改变状态的线程正确使用条件变量,Linux 将保证当条件改变的时候由于一个条件变量的状态被阻塞的线程均能够被激活。

GNU/Linux 刚好提供了这个机制,每个条件变量都必须与一个互斥体共同使用,以防止这种竞争状态的发生。这种设计下,线程函数应遵循以下步骤:

  1. thread_function首先锁定互斥体并且读取标志变量的值。
  2. 如果标志变量已经被设定,该线程将互斥体解锁然后执行工作函数
  3. 如果标志没有被设置,该线程自动锁定互斥体并开始等待条件变量的信号

这里最关键的特点就在第三条。这里,GNU/Linux系统允许你用一个原子操作完成解除互斥体锁定和等待条件变量信号的过程而不会被其它线程在中途插入执行。这就避免了在thread_function中检测标志和等待条件变量的过程中其它线程修改标志变量并对条件变量发送信号的可能性。

互斥锁是类型为pthread_mutex_t的变量,和互斥锁类似,初始化也两种方法:

一种是静态初始化,给锁变量赋值PTHREAD_COND_INITIALIZER,

       pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

一种动态初始化,使用函数pthread_cond_init,

       int pthread_cond_init(pthread_cond_t *restrict cond,  const pthread_condattr_t *restrict attr); 

其中第一个参数是一个指向pthread_cond_t变量的指针。第二个参数是一个指向条件变量属性对象的指针;这个参数在 GNU/Linux 系统中是被忽略的。

函数pthread_cond_wait()使线程阻塞在一个条件变量上。函数原型如下:

       #include <pthread.h>
       int pthread_cond_wait(pthread_cond_t *restrict cond,  pthread_mutex_t *restrict mutex);

第一个参数是指向一个 pthread_cond_t 类型变量的指针,第二个参数是指向一个pthread_mutex_t类型变量的指针。当调用
pthread_cond_wait 的时候,互斥体对象必须已经被调用线程锁定。这个函数以一个原子操作解锁互斥体并锁定条件变量等待信号。当信号到达且调用线程被解锁之后,pthread_cond_wait自动申请锁定互斥体对象。

函数传入的参数mutex用于保护条件,因为我们在调用pthread_cond_wait时,如果条件不成立我们就进入阻塞,但是进入阻塞这个期间,如果条件变量改变了的话,那我们就漏掉了这个条件。因为这个线程还没有放到等待队列上,所以调用pthread_cond_wait前要先锁互斥量,即调用pthread_mutex_lock(),pthread_cond_wait在把线程放进阻塞队列后,自动对mutex进行解锁,使得其它线程可以获得加锁的权利。这样其它线程才能对临界资源进行访问并在适当的时候唤醒这个阻塞的进程。当pthread_cond_wait返回的时候又自动给mutex加锁。

函数pthread_cond_signal()用来释放被阻塞在条件变量cond上的一个线程。多个线程阻塞在此条件变量上时,哪一个线程被唤醒是由线程的调度策略所决定的。要注意的是,必须用保护条件变量的互斥锁来保护这个函数,否则条件满足信号又可能在测试条件和调用pthread_cond_wait函数之间被发出,从而造成无限制的等待。函数原型如下:

       #include <pthread.h>
       int pthread_cond_signal(pthread_cond_t *cond);

下面给一个示例:

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

int gval = 0;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
void *func1_th(void *arg)
{
    pthread_mutex_lock(&mutex);
    if (5 != gval) {
        printf("=======%s->%d===thread 1 wait start!====\n", __func__, __LINE__);
        pthread_cond_wait(&cond, &mutex); //当条件不满足时,此时线程1会自动对mutex进行解锁,使得其它线程可以获得加锁的权利,当收到苏醒时会在执行加锁操作
    }
    /**
     * do something here
     */
    printf("=======%s->%d====thread 1 do something!===\n", __func__, __LINE__);
    pthread_mutex_unlock(&mutex);
    return NULL;
}
void *func2_th(void *arg)
{
    int i;
    pthread_mutex_lock(&mutex);
    for (i = 0; i < 10; i++) {
        printf("=======%s->%d=======\n", __func__, __LINE__);
        sleep(1);
        gval++;
        if (5 == gval) {
            pthread_cond_signal(&cond); //当条件满足时,线程2发送信号,线程1收到信号会解除阻塞状态。
            printf("=======%s->%d===signal====\n", __func__, __LINE__);
        }
        pthread_mutex_unlock(&mutex);
    }
    return NULL;
}
int main(int argc, const char *argv[])
{
    pthread_t tid1, tid2;
    if (0 != pthread_create(&tid1, NULL, func1_th, NULL)) {
        printf("pthread_create failed!\n");
        return -1;
    }
    sleep(1);     //让线程1先执行,进入等待状态
    if (0 != pthread_create(&tid2, NULL, func2_th, NULL)) {
        printf("pthread_create failed!\n");
        return -1;
    }
    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);
    return 0;
}

另一个用来阻塞线程的函数是pthread_cond_timedwait()。函数原型如下:

       #include <pthread.h>
       int pthread_cond_timedwait(pthread_cond_t *restrict cond,  pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime); 

pthread_cond_timedwait 和pthread_cond_wait 一样,自动解锁互斥量及等待条件变量, 但它还限定了等待时间。如果在 abstime 指定的时间内 cond 未触发,互斥量 mutex 被重 新加锁,并返回错误
ETIMEDOUT 。

。函数pthread_cond_broadcast(pthread_cond_t *cond)用来唤醒所有被阻塞在条件变量cond上的线程。这些线程被唤醒后将再次竞争相应的互斥锁,所以必须小心使用这个函数。函数原型如下:

       #include <pthread.h>
       int pthread_cond_broadcast(pthread_cond_t *cond);

本节源码下载:

点击打开链接

时间: 2024-08-07 17:02:05

Linux互斥与同步应用(二):posix线程同步的相关文章

Linux程序设计学习笔记----多线程编程之线程同步之条件变量

转载请注明出处:http://blog.csdn.net/suool/article/details/38582521. 基本概念与原理 互斥锁能够解决资源的互斥访问,但是在某些情况下,互斥并不能解决问题,比如两个线程需 要互斥的处理各自的操作,但是一个线程的操作仅仅存在一种条件成立的情况下执行,一旦错过不可再重现,由于线程间相互争夺cpu资源,因此在条件成立的时候,该线程不一定争夺到cpu而错过,导致永远得不到执行..... 因此需要某个机制来解决此问题,更重要的是,线程仅仅只有一种情况需要执

C#中的线程(二)线程同步

C#中的线程(二)线程同步 Keywords:C# 线程Source:http://www.albahari.com/threading/Author: Joe AlbahariTranslator: Swanky WuPublished: http://www.cnblogs.com/txw1958/Download:http://www.albahari.info/threading/threading.pdf 第二部分:线程同步基础 同步要领 下面的表格列展了.NET对协调或同步线程动作的

win32多线程 (二)线程同步之临界区 (critical sections)

所谓critical sections 意指一小块“用来处理一份被共享之资源”的程序代码.你可能必须在程序的许多地方处理这一块可共享的资源.所有这些程序代码可以被同一个critical  section 保护起来.为了阻止问题发生,一次只能有一个线程获准进入critical  section 中.critical section 并不是核心对象.使用方法: CRITICAL_SECTION g_section; 1:初始化 InitializeCriticalSection(&g_section

线程同步——内核对象实现线程同步——信号量

1 /* 2 3 信号量内核对象 4 信号量与其它内核量相同,包含一个使用计数,除此之外还包含两个量. 5 一个最大资源计数和一个当前资源计数. 6 信号量规则如下: 7 如果当前资源计数大于0,那么信号量处于触发状态. 8 如果当前资源计数等于0,那么信号量处于未触发状态. 9 系统绝不会让当前资源计数变为负数. 10 当前资源计数绝不会大于最大资源计数. 11 12 下面我们看一下信号量的创建函数 13 HANDLE CreateSemaphore( 14 LPSECURITY_ATTRIB

线程同步——内核对象实现线程同步——等待函数

1 对于内核对象实现线程同步,不得不提三点: 2 1)大多数内核对象既有触发也有未触发两个状态 3 比如:进程.线程.作业.文件流.事件.可等待的计时器.信号量.互斥量 4 2)等待函数:等待函数使线程自愿进入等待状态,直到指定的内核对象变为触发状态为止, 5 说道等待我们最喜欢不过了,因为这样不会浪费我们宝贵的CPU时间. 6 3)对于自动重置对象来说,当对象被触发时,函数会自动检测到(手动重置对象为触发是,函数也能检测到), 7 并开始执行,但是在函数会在返回之前使事件变为非触发状态. 8

线程同步——用户模式下线程同步——Interlocked实现线程同步

1 线程同步分为用户模式下的线程同步和内核对象的线程同步. 2 3 当然用户模式下的线程同步实现速度比内核模式下快,但是功能也有局 4 5 6 7 8 //1.利用原子访问: Interlocked系列函数,关于Interlocked系列函数,我需要知道的是他们,执行的极快 9 10 //(1)首先是InterlockedExchangeAdd兄弟函数, 11 //第一个参数 Addend 代表进行操作数的地址, 12 //第二个参数 Value 代表增加的值,如果是想进行减法,传负数即可 13

线程同步——内核对象实现线程同步——事件内核对象

1 事件内核对象 2 3 事件类型对象有两种不同类型,手动重置和自动重置 4 手动重置:当一个手动重置对象被触发时候,等待该对象的所有线程变为可调度. 5 自动重置:当一个自动重置对象被触发时,只有一个等待该事件的线程会变为可调度 6 7 下面是一个创建事件内核对象的函数: 8 HANDLE CreateEvent( 9 LPSECURITY_ATTRIBUTES lpEventAttributes, 10 BOOL bManualReset, 11 BOOL bInitialState, 12

线程同步——内核对象实现线程同步——可等待计时器内核对象

1 可等待计时器 2 可等待计时器是这样一种内核对象,他们会在某个指定的时间触发或每隔一段时间触发一次. 5 下面我们来介绍一下创建可等待计时器函数: 6 7 HANDLE CreateWaitableTimer( 8 LPSECURITY_ATTRIBUTES lpTimerAttributes, 9 BOOL bManualReset, 10 LPCSTR lpTimerName ); 11 第一.三个参数我想我也不用介绍了哈,这是创建内核对象基本都会有的参数. 12 第二个参数bManua

线程同步——用户模式下线程同步——Slim读写锁实现线程同步

1 //Slim读/写锁实现线程同步 2 SRWlock 的目的和关键段相同:对同一资源进行保护,不让其它线程访问. 3 但是,与关键段不同的是,SRWlock允许我们区分哪些想要读取资源的线程(读取者线程) 4 和哪些想要更新资源值的线程(写入者线程).让所有读取者资源在同一时刻访问共享资源应该是 5 可行的,这是因为仅仅读取资源并不存在破坏数据的风险.只有当写入者线程想要对资源进行更新时才需要同步. 6 这种情况下,写入者线程应该独占资源访问权:任何线程,无论是读取还是写入者线程,都不许访问

线程同步——用户模式下线程同步——关键段实现线程同步

1 //2.使用关键段实现线程同步 2 使用时必须有以下几个步骤 3 //(1)必须先定义 CRITICAL_SECTION 结构 4 CRITICAL_SECTION g_cs; 5 //(2)初始化关键段 CRITICAL_SECTION 6 InitializeCriticalSection(&g_cs); 7 //(3)在线程中调用 8 DWORD WINAPI ThreadFunOne(PVOID pvParam) 9 { 10 EnterCriticalSection(&g_c