Android NDK开发之旅33--NDK-Linux入门之POSIX线程原语

POSIX

POSIX是一种标准,例如有多线程编程标准、网络编程标准等。

POSIX多线程

Linux下,一般多线程的实现由POSIX多线程编程实现。Android系统属于Linux系统,因此NDK原生支持POSIX多线程编程。

Windows平台一般用Windows自带的API。

Visual Studio平台搭建POSIX多线程环境

因为POSIX多线程是Linux的,因此如果需要在Visual Studio下开发,需要搭建可开发环境。

  1. 首先需要下载POSIX,地址为:ftp://sourceware.org/pub/pthreads-win32/pthreads-w32-2-9-1-release.zip
  2. 创建VS空项目
  3. 添加包含目录:pthreads-w32-2-9-1-release\Pre-built.2\include以及pthreads-w32-2-9-1-release\pthreads.2
  4. 添加库目录:pthreads-w32-2-9-1-release\Pre-built.2\lib\x86
  5. 添加附加依赖项:pthreadVC2.lib
  6. 把pthreads-w32-2-9-1-release\Pre-built.2\dll\x86下面的动态库文件复制到工程的正确位置。

POSIX的编译:

  1. VS平台中直接编译运行即可。
  2. 在Linux平台中采用gcc编译(先编译生成目标文件然后链接生成可执行程序):gcc test.c -o test -lpthread,执行:./test。

POSIX帮助文档的查看:

  1. 在Linux系统中,安装POSIX帮助文档:sudo apt-get install manpages-posix-dev
  2. 列出所有函数man -k pthread;查看某个函数:man pthread_create

创建线程与线程的结束

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

//必须引入的头文件
#include "pthread.h"

//一个相当于Java的run方法
void *start_fun(void* arg){

    //得到线程创建的参数
    char* no = (char*)arg;
    int i = 0;
    for (; i < 10; i++){
        printf("%s thread : %d \n", no, i);
        if (i == 5){
            //线程自杀,需要返回参数
            pthread_exit((void*)2);
            //线程他杀
            //pthread_cancel()
        }
    }

    //run方法执行完,线程结束,返回
    return (void*)1;
}

void main(){

    printf("main thread\n");

    pthread_t thread;
    //创建线程,指定run方法,并且可以传入参数,在run方法的arg中可以取出
    pthread_create(&thread, NULL, start_fun, "no1");

    void* r_val;
    //等待线程结束,获取线程返回参数
    pthread_join(thread, &r_val);
    printf("return value : %d", (int)r_val);

    system("pause");
}

在代码中:

  1. 通过pthread_create创建线程,需要传入一个函数指针,相当于Java线程中的run方法。然后还需要传参,参数可以在run方法中取出。
  2. 线程被创建以后,就会执行“run”方法,该方法中可以拿到线程创建的参数,可以自杀掉线程。线程的结束需要参数。
  3. 可以通过pthread_join方法等待线程结束,并且可获取线程结束的参数。

我们创建两个线程:

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

int i = 0;

//一个相当于Java的run方法
void *start_fun(void* arg){

    //得到线程创建的参数
    char* no = (char*)arg;
    for (; i < 10; i++){
        Sleep(10);
        printf("%s thread : %d \n", no, i);
    }
    i = 0;

    //run方法执行完,线程结束,返回
    return (void*)1;
}

void main(){

    printf("main thread\n");

    pthread_t thread1;
    pthread_t thread2;
    //创建线程,指定run方法,并且可以传入参数,在run方法的arg中可以取出
    pthread_create(&thread1, NULL, start_fun, "no1");
    pthread_create(&thread2, NULL, start_fun, "no2");

    void* r_val1;
    void* r_val2;
    //等待线程结束,获取线程返回参数
    pthread_join(thread1, &r_val1);
    pthread_join(thread2, &r_val2);
    printf("return value : %d\n", (int)r_val1);
    printf("return value : %d\n", (int)r_val2);

    system("pause");
}

打印的结果如下:

main thread
no2 thread : 0
no1 thread : 0
no2 thread : 2
no1 thread : 2
no1 thread : 4
no2 thread : 4
no1 thread : 5
return value : 2
return value : 2
请按任意键继续. . .

可见,两个线程是并行执行的。i变量同时被两个线程访问。但是我们现在要求线程1先执行完,然后才到线程2执行,那么两个线程i的所有情况都会被打印出来。

这时候我们需要使用互斥锁:

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

int i = 0;

//互斥锁
pthread_mutex_t m;

//一个相当于Java的run方法
void *start_fun(void* arg){

    //加锁
    pthread_mutex_lock(&m);

    //得到线程创建的参数
    char* no = (char*)arg;
    for (; i < 10; i++){
        Sleep(10);
        printf("%s thread : %d \n", no, i);
    }
    i = 0;

    //解锁
    pthread_mutex_unlock(&m);

    //run方法执行完,线程结束,返回
    return (void*)1;
}

void main(){

    printf("main thread\n");

    //初始化互斥锁
    pthread_mutex_init(&m, NULL);

    pthread_t thread1;
    pthread_t thread2;
    //创建线程,指定run方法,并且可以传入参数,在run方法的arg中可以取出
    pthread_create(&thread1, NULL, start_fun, "no1");
    pthread_create(&thread2, NULL, start_fun, "no2");

    void* r_val1;
    void* r_val2;
    //等待线程结束,获取线程返回参数
    pthread_join(thread1, &r_val1);
    pthread_join(thread2, &r_val2);
    printf("return value : %d\n", (int)r_val1);
    printf("return value : %d\n", (int)r_val2);

    //销毁互斥锁
    pthread_mutex_destroy(&m);

    system("pause");
}

输出的结果如下:

main thread
no1 thread : 0
no1 thread : 1
no1 thread : 2
no1 thread : 3
no1 thread : 4
no1 thread : 5
no1 thread : 6
no1 thread : 7
no1 thread : 8
no1 thread : 9
no2 thread : 0
no2 thread : 1
no2 thread : 2
no2 thread : 3
no2 thread : 4
no2 thread : 5
no2 thread : 6
no2 thread : 7
no2 thread : 8
no2 thread : 9
return value : 1
return value : 1
请按任意键继续. . .

在代码中:

  1. 我们通过pthread_mutex_init初始化了一把互斥锁,最后通过pthread_mutex_destroy进行销毁。
  2. 在线程执行的时候,我们可以通过pthread_mutex_lock、pthread_mutex_unlock进行加锁和解锁。
  3. 使用互斥锁可以解决线程死锁(ABBA)的问题。

互斥锁是先让一个线程做完,然后另外一个线程做。还有一种情况就是,一个线程先执行,生产,然后另外一个线程就会去消费。

其实视频解码的绘制使用的就是生产者--消费者的模式。图片的下载显示也是基于这种模式。比如说我们生产者生成的产品,放到一个队列里面,当生产者生产出产品的时候就会发送信号通知消费者去消费,例如RTMP推流的时候,我们本地采集音视频的时候就需要一种队列,因为本地的压缩比网络上传要快。

使用这一种模式,就需要条件变量。例子:

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

//模拟产品队列
int productNum = 0;

//互斥锁
pthread_mutex_t m;
//条件变量
pthread_cond_t c;

void *produce(void* arg){

    char* no = (char*)arg;

    for (;;){
        //加锁
        pthread_mutex_lock(&m);

        //生产者生产产品
        productNum++;
        printf("%s生产产品:%d\n", no, productNum);
        //通知消费者进行消费
        pthread_cond_signal(&c);

        //解锁
        pthread_mutex_unlock(&m);

        Sleep(100);
    }
    return (void*)1;
}

void *comsume(void* arg){

    char* no = (char*)arg;

    for (;;){
        pthread_mutex_lock(&m);
        //使用while是为了防止惊群效应唤醒条件变量
        while (productNum == 0){
            //1.没有产品可以消费,等待生产者生产,即等待条件变量被唤醒
            //2.释放互斥锁,使得其他消费者可以进来等待
            //3.被唤醒的时候,解除阻塞,重新申请获得互斥锁,保证只有一个消费者消费
            pthread_cond_wait(&c, &m);
        }
        productNum--;
        printf("%s消费者消费产品:%d\n", no, productNum);
        pthread_mutex_unlock(&m);
        Sleep(1000);
    }
    return (void*)1;
}

void main(){

    printf("main thread\n");

    //初始化互斥锁
    pthread_mutex_init(&m, NULL);
    //初始化条件变量
    pthread_cond_init(&c, NULL);

    pthread_t thread_producer;
    pthread_t thread_comsumer;
    //创建线程,指定run方法,并且可以传入参数,在run方法的arg中可以取出
    pthread_create(&thread_producer, NULL, produce, "producer");
    pthread_create(&thread_comsumer, NULL, comsume, "comsumer");

    //等待线程结束,获取线程返回参数
    pthread_join(thread_producer, NULL);
    pthread_join(thread_comsumer, NULL);

    //销毁互斥锁
    pthread_mutex_destroy(&m);
    //销毁条件变量
    pthread_cond_destroy(&c);
    system("pause");
}

输出的结果如下:

main thread
producer生产产品:1
comsumer消费者消费产品:0
producer生产产品:1
producer生产产品:2
producer生产产品:3
producer生产产品:4
producer生产产品:5
producer生产产品:6
producer生产产品:7
producer生产产品:8
producer生产产品:9
comsumer消费者消费产品:8
producer生产产品:9
producer生产产品:10
producer生产产品:11
producer生产产品:12
producer生产产品:13
producer生产产品:14
producer生产产品:15
producer生产产品:16
producer生产产品:17
comsumer消费者消费产品:16

这里我通过Sleep的方式控制了生产者与消费者的效率,一般来说生产的速度要比消费的速度快。

上面是只有一个生产者和一个消费者的示例代码。一般开说,生产者和消费者都会有多个。这里我们通过线程数组的方式来实现。

示例代码如下:

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

#define NUM_PRODUCER 2
#define NUM_COMSUMER 2
pthread_t threads[NUM_PRODUCER + NUM_COMSUMER];

int productNum = 0;

//互斥锁
pthread_mutex_t m;
//条件变量
pthread_cond_t c;

void *produce(void* arg){

    int no = (int)arg;

    for (;;){
        //加锁
        pthread_mutex_lock(&m);

        //生产者生产产品
        productNum++;
        printf("%d生产产品:%d\n", no, productNum);
        //通知消费者进行消费
        pthread_cond_signal(&c);

        //解锁
        pthread_mutex_unlock(&m);

        Sleep(100);
    }
    return (void*)1;
}

void *comsume(void* arg){

    int no = (int)arg;

    for (;;){
        pthread_mutex_lock(&m);
        while (productNum == 0){
            //没有产品可以消费,等待生产者生产
            pthread_cond_wait(&c, &m);
        }
        productNum--;
        printf("%d消费者消费产品:%d\n", no, productNum);
        pthread_mutex_unlock(&m);
        Sleep(1000);
    }
    return (void*)1;
}

void main(){

    printf("main thread\n");

    //初始化互斥锁
    pthread_mutex_init(&m, NULL);
    //初始化条件变量
    pthread_cond_init(&c, NULL);

    int i = 0;
    //创建生产者线程
    for (i = 0; i < NUM_PRODUCER; i++){
        pthread_create(&threads[i], NULL, produce, (void*)i);
    }


    //创建消费者线程
    for (i = 0; i < NUM_COMSUMER; i++){
        pthread_create(&threads[NUM_PRODUCER + i], NULL, comsume, (void*)i);
    }

    //等待线程结束,获取线程返回参数
    for (i = 0; i < NUM_PRODUCER + NUM_COMSUMER; i++){
        pthread_join(threads[i], NULL);
    }

    //销毁互斥锁
    pthread_mutex_destroy(&m);
    //销毁条件变量
    pthread_cond_destroy(&c);
    system("pause");
}

如果觉得我的文字对你有所帮助的话,欢迎关注我的公众号:

公众号:Android开发进阶

我的群欢迎大家进来探讨各种技术与非技术的话题,有兴趣的朋友们加我私人微信huannan88,我拉你进群交(♂)流(♀)

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

推荐阅读更多精彩内容

  • 引用自多线程编程指南应用程序里面多个线程的存在引发了多个执行线程安全访问资源的潜在问题。两个线程同时修改同一资源有...
    Mitchell阅读 1,975评论 1 7
  • linux线程同步 信号灯:与互斥锁和条件变量的主要不同在于"灯"的概念,灯亮则意味着资源可用,灯灭则意味着不可用...
    鲍陈飞阅读 682评论 0 2
  • 文/新鲜 《创新自信力》 腾跃:从设计思维到创意自信 1.从技术可行性、商业可行性和人的期望值之间找到共通点,也就...
    新鲜wendy阅读 807评论 0 0
  • 准备考研的日子,经常会很迷茫,常常想些有的没的, 回想起小时候的日子,觉得当时的自己是最纯粹的,比起现在而言,...
    二木子阅读 241评论 0 1