linux c++ 多线程 【三】

今天得空继续扫了一下(https://computing.llnl.gov/tutorials/pthreads/这次没有用c++,直接参考的tutorial中的c语言实现)pthread中提供的另一种线程同步的方法:condition variables

既然已经有了mutex,为什么还要有condition variables这样的技术手段呢?

原文的阐述是:“While mutexes implement synchronization by controlling thread access to data, condition variables allow threads to synchronize based upon the actual value of data.”

按照我自己的理解就是:

1)mutex的作用仅限于是否允许某个子线程去访问、修改某个内存变量,以此做到同步;提供的synchronize逻辑判断仅限于:can or cannot

2)condition variables的作用比单纯mutex要强一些,可以与mutex联合使用;提供的synchronize机制可以是:if  condition then do work

上述的理解,也是我在实现过一个demo之后得出的,下面阐述一下simple demo。

考虑这样一个问题:

1)有一个全局的计数变量int count,各个线程均可以访问

2)有两个子线程对count进行累加操作

3)另外还有一个监控子线程,对于count的值是敏感的:当count累加到某个临界值的时候,触发这个子线程完成任务

代码如下:

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

#define NUM_THREADS 3
#define TCOUNT 10
#define COUNT_LIMIT 12

int count = 0;
int thread_id[3] = {0,1,2};

pthread_mutex_t count_mutex;
pthread_cond_t count_threshold_cv;

void *inc_count(void *t)
{
    int i;
    long my_id = (long)t;

    for (i=0; i<TCOUNT; i++){
        pthread_mutex_lock(&count_mutex);
        count++;
        if ( count==COUNT_LIMIT )
        {
            pthread_cond_signal(&count_threshold_cv);
            printf("inc_count(): thread %ld, count = %d Threshold reached \n", my_id, count );
        }
        printf("inc_count() : thread %ld, count = %d, unlocking mutex \n", my_id, count);
        pthread_mutex_unlock(&count_mutex);
        sleep(1);
    }
    pthread_exit((void *) 0);
}

void *watch_count(void *t)
{
    long my_id = (long)t;
    printf("Starting watch_count(): thread %ld\n", my_id);

    pthread_mutex_lock(&count_mutex);
    while (count<COUNT_LIMIT)
    {
        pthread_cond_wait(&count_threshold_cv, &count_mutex);
        printf("watch_count(): thread %ld Condition signale received.\n", my_id);
        count += 125;
        printf("watch_count(): thread %ld count now = %d.\n",my_id, count);
    }
    pthread_mutex_unlock(&count_mutex);
    pthread_exit((void *) 0);
}

int main(int argc, char *argv[])
{
    int i, rc;
    long t1=1, t2=2, t3=3;
    pthread_t threads[3];
    pthread_attr_t attr;

    pthread_mutex_init(&count_mutex, NULL);
    pthread_cond_init(&count_threshold_cv, NULL);

    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 (i=0; i<NUM_THREADS; ++i ){
        pthread_join(threads[i], NULL);
    }

    printf("Main(): Waited on %d threads. Done. \n", NUM_THREADS);

    pthread_attr_destroy(&attr);
    pthread_mutex_destroy(&count_mutex);
    pthread_cond_destroy(&count_threshold_cv);
    pthread_exit((void *)0);
}

先给输出结果,再解释这段代码是怎么实现上述的问题的:

两种子线程:

1)void *inc_count(void *t)实现了给全局变量count的累加计数功能

2)void *watch_count(void *t)实现了监控全局变量count的值并触发任务的功能

下面记录我自己分析每个子线程的实现逻辑的思路顺序:

1)watch_count对count进行监控:

 1 void *watch_count(void *t)
 2 {
 3     long my_id = (long)t;
 4     printf("Starting watch_count(): thread %ld\n", my_id);
 5
 6     pthread_mutex_lock(&count_mutex);
 7     while (count<COUNT_LIMIT)
 8     {
 9         pthread_cond_wait(&count_threshold_cv, &count_mutex);
10         printf("watch_count(): thread %ld Condition signale received.\n", my_id);
11         count += 125;
12         printf("watch_count(): thread %ld count now = %d.\n",my_id, count);
13     }
14     pthread_mutex_unlock(&count_mutex);
15     pthread_exit((void *) 0);
16 }

a. 为什么line 6要先对count_mutex上锁呢?

  因为,当watch_count在判断count的值的时候,必须“独揽”对count的操作权利;如果正判断count的时候,count被其他线程改变了或者怎样,逻辑就很可能是错误的(这一点在这个系列的前几篇日志中说明了

b. “pthread_cond_wait(&count_threshold_cv, &count_mutex);”的作用是什么?

  首先解释下参数:pthread_cond_t count_threashold_cv正是这一节提到的条件变量;count_mutex是用于全局变量count的同步锁

  这个语句相当于做了如下两件件事情:

    b1. 先阻塞当前线程

    b2. 判断count_threshold_cv这个条件变量是否被激活:

       b21. 如果没被激活,自动放开对count_mutex的锁(这个解锁是针对line 6对count_mutex上的锁);当前线程继续保持被阻塞状态

       b22. 如果被激活,唤醒count_mutex的锁;当前的线程不被阻塞,继续往下执行(由于line 6一直锁着当前线程,则需要线程执行完毕前对count_mutex解锁) 

c. 读完上述的代码,马上产生两个疑问:

    c1. count_threshold_cv的初始状态到底激活没激活是谁管的?

      是main()函数中的“pthread_cond_init(&count_threshold_cv, NULL);”语句,初始化count_threshold_cv没激活的。

    c2. count_threshold_cv的激活是谁管的呢?

      这就要再分析另一个子线程函数inc_count了

2)inc_count子线程对count进行累加操作 & 对条件变量count_threshold_cv进行激活操作

 1 void *inc_count(void *t)
 2 {
 3     int i;
 4     long my_id = (long)t;
 5
 6     for (i=0; i<TCOUNT; i++){
 7         pthread_mutex_lock(&count_mutex);
 8         count++;
 9         if ( count==COUNT_LIMIT )
10         {
11             pthread_cond_signal(&count_threshold_cv);
12             printf("inc_count(): thread %ld, count = %d Threshold reached \n", my_id, count );
13         }
14         printf("inc_count() : thread %ld, count = %d, unlocking mutex \n", my_id, count);
15         pthread_mutex_unlock(&count_mutex);
16         sleep(1);
17     }
18     pthread_exit((void *) 0);
19 }

这个函数比较直观:

1)Loop中每次都对count加1(当然了,对count进行操作时一定要对count_mutex加锁,line 7的语句

2)对count的值进行判断:

    a. 如果count不等于临界值:do some work,并对count_mutex进行解锁;然后再sleep一下(目的是人工给其他线程获得count_mutex控制权的机会

    b. 如果count等于临界值:

      b1. 激活条件变量count_threshold_cv

      b2. 唤醒由于等待count_threshold_cv而被阻塞的线程(这个demo里只有一个等着的线程,如果多个线程都等待count_threshold_cv呢?这个过后值得思考一下

  b2里有一个细节问题:当count_threshold_cv被激活后,watch_count是马上执行呢?还是等着激活count_threshold_cv的这个线程执行执行完再被“真正”唤醒呢?用结果说话:

  显然,即便是count_threshold_cv被激活之后,watch_count也没有马上执行;而是等着inc_count中的count_mutex被解锁后,再执行被激唤醒的watch_count线程;watch_count线程被唤醒的同时,watch_count线程又重新夺回了count_mutex的占有权。

上面的demo已经大概解释说明了condition variables是怎么使用的,有几个细节还应该扣一扣:

细节一

  这里其实还有地方没十分确定:如上图,当count_threshold_cv被thread3激活了,并且thread3线程已经对count_mutex执行unlocking了,这个时候会不会存在thread1(thread1是watch_count线程)和thread2(thread2是另一个inc_count线程)同时竞争count_mutex的情况呢?根据输出的结果来看,此时可能不存在thread1和thread2竞争的情况,操作系统赋予了被唤醒的thread1对count_mutex的优先上锁权。

细节二

  如果调用pthread_cond_wait的线程里面,没有对count_mutex的限制,那么运行的结果如何呢?我稍微修了一下代码,增加了一个全局的pthread_mutex_t test,并在main中将其初始化,并修改inc_count函数和watch_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++;
        if ( count==COUNT_LIMIT )
        {
            pthread_cond_signal(&count_threshold_cv);
            sleep(1);
            printf("inc_count(): thread %ld, count = %d Threshold reached \n", my_id, count );
        }
        printf("inc_count() : thread %ld, count = %d, unlocking mutex \n", my_id, count);
        pthread_mutex_unlock(&count_mutex);
        sleep(1);
    }
    pthread_exit((void *) 0);
}

void *watch_count(void *t)
{
    long my_id = (long)t;
    printf("Starting watch_count(): thread %ld\n", my_id);

    pthread_mutex_lock(&test);
    int i=0;
    while (i<1)
    {
        pthread_cond_wait(&count_threshold_cv, &test);
        printf("watch_count(): thread %ld Condition signale received.\n", my_id);
        count += 125;
        printf("watch_count(): thread %ld count now = %d.\n",my_id, count);
        i++;
    }
    pthread_mutex_unlock(&test);
    pthread_exit((void *) 0);
}

运行结果如下:

通过这个运行结果,可以看到:一旦pthread_cond_signal之后,由于没有count_mutex的上锁限制,watch_count线程立刻执行了(这里在inc_count函数中加了sleep(1)就是故意等着,看watch_count有没有立即不受阻塞执行)。因此,从反面再次验证了,调用pthread_cond_signal的线程仅仅是发送一个激活watch_count线程的信号;如果watch_count中受到了count_mutex的限制,那么还是要等到inc_count中对count_mutex解锁后才会被真正激活。

下面这两个连接,对pthread_cond_wait()和pthread_cond_signal()有比较详细的解释

https://computing.llnl.gov/tutorials/pthreads/man/pthread_cond_wait.txt

https://computing.llnl.gov/tutorials/pthreads/man/pthread_cond_signal.txt

但实际工作中,还得去试验一下代码运行的平台系统,对pthread是怎样一种具体的实现策略。

   

时间: 2024-10-10 22:10:55

linux c++ 多线程 【三】的相关文章

linux pthread多线程编程模板

pthread_create() 创建线程,pthread_join()让线程一直运行下去. 链接时要加上-lpthread选项. pthread_create中, 第三个参数为线程函数,定义如下: void * heartbeat_thread() { ... } 下面是main.c : #include <pthread.h> pthread_t thread[MAX_THREAD_NUM]; pthread_mutex_t cache_mutex; pthread_mutex_t var

Linux 的多线程编程的高效开发经验(转)

http://www.ibm.com/developerworks/cn/linux/l-cn-mthreadps/ 背景 Linux 平台上的多线程程序开发相对应其他平台(比如 Windows)的多线程 API 有一些细微和隐晦的差别.不注意这些 Linux 上的一些开发陷阱,常常会导致程序问题不穷,死锁不断.本文中我们从 5 个方面总结出 Linux 多线程编程上的问题,并分别引出相关改善的开发经验,用以避免这些的陷阱.我们希望这些经验可以帮助读者们能更好更快的熟悉 Linux 平台的多线程

转载自~浮云比翼:Step by Step:Linux C多线程编程入门(基本API及多线程的同步与互斥)

Step by Step:Linux C多线程编程入门(基本API及多线程的同步与互斥) 介绍:什么是线程,线程的优点是什么 线程在Unix系统下,通常被称为轻量级的进程,线程虽然不是进程,但却可以看作是Unix进程的表亲,同一进程中的多条线程将共享该进程中的全部系统资源,如虚拟地址空间,文件描述符和信号处理等等.但同一进程中的多个线程有各自的调用栈(call stack),自己的寄存器环境(register context),自己的线程本地存储(thread-local storage). 一

[转]Linux 的多线程编程的高效开发经验

Linux 平台上的多线程程序开发相对应其他平台(比如 Windows)的多线程 API 有一些细微和隐晦的差别.不注意这些 Linux 上的一些开发陷阱,常常会导致程序问题不穷,死锁不断.本文中我们从 5 个方面总结出 Linux 多线程编程上的问题,并分别引出相关改善的开发经验,用以避免这些的陷阱.我们希望这些经验可以帮助读者们能更好更快的熟悉 Linux 平台的多线程编程. 我们假设读者都已经很熟悉 Linux 平台上基本的线程编程的 Pthread 库 API .其他的第三方用以线程编程

linux下多线程编程

最近研究mysql源码,各种锁,各种互斥,好在我去年认真学了<unix环境高级编程>, 虽然已经忘得差不多了,但是学过始终是学过,拿起来也快.写这篇文章的目的就是总结linux 下多线程编程,作为日后的参考资料. 本文将介绍linux系统下多线程编程中,线程同步的各种方法.包括: 互斥量(mutex) 读写锁 条件变量 信号量 文件互斥 在介绍不同的线程同步的方法之前,先简单的介绍一下进程和线程的概念, 它们的优缺点,线程相关的API,读者——写者问题和哲学家就餐问题. 基础知识 1. 进程和

IO复用、多进程和多线程三种并发编程模型

I/O复用模型 I/O复用原理:让应用程序可以同时对多个I/O端口进行监控以判断其上的操作是否可以进行,达到时间复用的目的.在书上看到一个例子来解释I/O的原理,我觉得很形象,如果用监控来自10根不同地方的水管(I/O端口)是否有水流到达(即是否可读),那么需要10个人(即10个线程或10处代码)来做这件事.如果利用某种技术(比如摄像头)把这10根水管的状态情况统一传达到某一点,那么就只需要1个人在那个点进行监控就行了,而类似与select或epoll这样的多路I/O复用机制就好比是摄像头的功能

linux之多线程frok(一)

linux下实现多线程有两种函数调用:一种是通过pthread.h里面已经封装好的函数调用,另一种是通过unistd.h里面的fork函数调用.前面已经已经列举了pthread的使用,下面来书fork的例子. 一.fork函数 简单的fork例子 #include <iostream> #include <unistd.h> #include <sys/types.h> using namespace std; int main() { pid_t pid; pid=f

Linux 的多线程编程的高效开发经验

http://www.ibm.com/developerworks/cn/linux/l-cn-mthreadps/ 背景 Linux 平台上的多线程程序开发相对应其他平台(比如 Windows)的多线程 API 有一些细微和隐晦的差别.不注意这些 Linux 上的一些开发陷阱,常常会导致程序问题不穷,死锁不断.本文中我们从 5 个方面总结出 Linux 多线程编程上的问题,并分别引出相关改善的开发经验,用以避免这些的陷阱.我们希望这些经验可以帮助读者们能更好更快的熟悉 Linux 平台的多线程

linux下多线程下载工具axel的编译安装

axel 是Linux 命令行下多线程的下载工具,支持断点续传,速度通常情况下是Wget的几倍 官方主页:http://axel.alioth.debian.org/ 源码下载: #curl -O http://pkgs.fedoraproject.org/repo/pkgs/axel/axel2.4.tar.gz/a2a762fce0c96781965c8f9786a3d09d/axel-2.4.tar.gz 编译安装: # tar -xvf axel-2.4.tar.gz  && cd

多线程(三) java中线程的简单使用

============================================= 原文链接:多线程(三) java中线程的简单使用 转载请注明出处! ============================================= java中,启动线程通常是通过Thread或其子类通过调用start()方法启动. 常见使用线程有两种:实现Runnable接口和继承Thread.而继承Thread亦或使用TimerTask其底层依旧是实现了Runnabel接口.考虑到java的