【从零开始的嵌入式生活】并发程序设计2——线程专题

请添加图片描述

今天开始一个比较常用的模块,就是并发编程,可以极大的提高我们程序的运行效率,这部分内容非常重要。那么,让我们开始吧。

🧑🏻作者简介:一个学嵌入式的年轻人
✨联系方式:2201891280(QQ)
📔源码地址:https://gitee.com/xingleigao/study_qianrushi
全文大约阅读时间: 60min



线程基础

QQ的多个功能:

  1. 接收输入
  2. 通讯
  3. 显示界面

为了实现多个功能可以使用线程。


进程特点

  • 进程有独立的地址空间(不能互相访问)
  • Linux为每个进程创建task_struct
  • 每个进程都参与内核调度,互不影响

线程

进程在切换时系统开销大,很多操作系统引入了轻量级进程LWP

  • 同一进程中的线程共享相同地址空间
  • Linux不区分进程、线程

线程特点

通常线程指的是共享相同地址空间的多个任务

使用多线程的好处:

  • 大大提高了任务切换的效率
  • 避免了额外的TLB & cache的刷新(进程上下文)

线程共享资源

  • 可执行的指令
  • 静态数据(全局变量)
  • 进程中打开的文件描述符
  • 当前工作目录
  • 用户ID
  • 用户组ID

线程私有资源

  • 线程ID (TID)
  • PC(程序计数器)和相关寄存器
  • 堆栈
  • 错误号 (errno)
  • 优先级
  • 执行状态和属性

Linux线程库

线程库功能

pthread线程库中提供了如下基本操作

  • 创建线程
  • 回收线程
  • 结束线程

同步和互斥机制

  • 信号量
  • 互斥锁

线程创建 – pthread_create

#include  <pthread.h>
int  pthread_create(pthread_t *thread, const
      pthread_attr_t *attr, void *(*routine)(void *), void *arg);
  • 成功返回0,失败时返回错误码
  • thread 线程对象
  • attr 线程属性,NULL代表默认属性
  • routine 线程执行的函数
  • arg 传递给routine的参数 ,参数是void * ,注意传递参数格式,

示例代码:

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

void * function1(void *arg){
       printf("this is thread function\n");
}

int main(){
       pthread_t tid;

       int err;
       err = pthread_create(&tid, NULL, function1, NULL);
       sleep(1);
       if(err != 0){
               printf("create thread:%s\n",strerror(err));
       }
       return 0;
}

注意:编译的时候需要加入-lpthread链接线程库!在线程中exit就会全部结束!


线程回收 – pthread_join

#include  <pthread.h>
int  pthread_join(pthread_t thread, void **retval);//*tid 和线程的返回值

成功返回0,失败时返回错误码
thread 要回收的线程对象
调用线程阻塞直到thread结束
*retval 接收线程thread的返回值


线程结束 – pthread_exit

结束当前线程
retval可被其他线程通过pthread_join获取
线程私有资源被释放
示例代码:

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

void * function1(void *arg){
       printf("this is thread function\n");
       sleep(1);
       pthread_exit("funct exit");
}

int main(){

       pthread_t tid;

       int err, i;
       err = pthread_create(&tid, NULL, function1, NULL);

       void *retval;
       pthread_join(tid, &retval);
       printf("retval = %s",(char *)retval);
       
       return 0;
}

其他函数

这部分就自己看man手册尝试用用吧

  • Pthread_join
  • Pthread_cancel
  • pthread_testcancel
  • pthread_setcancelstate
  • pthread_setcanceltype
  • Pthead_detach
  • pthread_attri_init

取消一个线程

  • int pthread_cancel(pthread_t thread);
  • void pthread_testcancel(void);
  • int pthread_setcancelstate(int state, int *oldstate);
  • PTHREAD_CANCEL_ENABLE
  • PTHREAD_CANCEL_DISABLE
  • int pthread_setcanceltype(int type, int *oldtype);
  • PTHREAD_CANCEL_DEFERRED
  • PTHREAD_CANCEL_ASYNCHRONOUS

线程间通信

线程共享同一进程的地址空间
优点:线程间通信很容易 通过全局变量交换数据
缺点:多个线程访问共享数据时需要同步或互斥机制


同步

同步(synchronization)指的是多个任务按照约定的先后次序相互配合完成一件事情
1968年,Edsgar Dijkstra基于信号量的概念 提出了一种同步机制
由信号量来决定线程是继续运行还是阻塞等待


信号量

信号量代表某一类资源,其值表示系统中该资源的数量
信号量是一个受保护的变量,只能通过三种操作来访问

  1. 初始化
  2. P操作(申请资源)
  3. V操作(释放资源)

Posix 信号量

posix中定义了两类信号量:

  1. 无名信号量(基于内存的信号量)
  2. 有名信号量

信号量初始化 – sem_init

#include  <semaphore.h>
int  sem_init(sem_t *sem, int pshared, unsigned int val);

成功时返回0,失败时EOF
sem 指向要初始化的信号量对象
pshared 0 – 线程间 1 – 进程间
val 信号量初值

信号量 – P / V 操作

#include  <semaphore.h>
int  sem_wait(sem_t  *sem);       P操作
int  sem_post(sem_t  *sem);       V操作

成功时返回0,失败时返回EOF
sem 指向要操作的信号量对象
示例代码:

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

char buf[100];
sem_t sem;

void *write(void *arg){
        while(1){
                fgets(buf, 20, stdin);
                sem_post(&sem);
        }
}

void *read(void *arg){
        while(1){
                sem_wait(&sem);
                printf("%s\n", buf);
                memset(buf, 0, sizeof(buf));
        }
}

int main(){
        pthread_t tid1, tid2;
        int re;
        sem_init(&sem, 0 , 0);
        re = pthread_create(&tid1, NULL, write, NULL);
        if(re != 0){
                printf("pthread_create: %s\n",strerror(re));
                exit(0);
        }
        re = pthread_create(&tid2, NULL, read, NULL);
        if(re != 0){
                printf("pthread_create: %s\n",strerror(re));
                exit(0);
        }
        while(1){
                sleep(1);
        }

}

线程通信——互斥

临界资源
一次只允许一个任务(进程、线程)访问的共享资源
临界区
访问临界区的代码
互斥机制
mutex互斥锁
任务访问临界资源前申请锁,访问完后释放锁


互斥锁初始化 – pthread_mutex_init

#include  <pthread.h>
int  pthread_mutex_init(pthread_mutex_t *mutex,
      const pthread_mutexattr_t *  attr);

成功时返回0,失败时返回错误码
mutex 指向要初始化的互斥锁对象
attr 互斥锁属性,NULL表示缺省属性
man 函数出现 No manual entry for pthread_mutex_xxx解决办法
apt-get install manpages-posix-dev


申请锁 – pthread_mutex_lock

#include  <pthread.h>
int  pthread_mutex_lock(pthread_mutex_t *mutex);

成功时返回0,失败时返回错误码
mutex 指向要初始化的互斥锁对象
如果无法获得锁,任务阻塞


释放锁 – pthread_mutex_unlock

#include  <pthread.h>
int  pthread_mutex_unlock(pthread_mutex_t *mutex);

成功时返回0,失败时返回错误码
mutex 指向要初始化的互斥锁对象
执行完临界区要及时释放锁
示例代码:

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

FILE *fp;

pthread_mutex_t mutex;

void *write1(void* arg){
        int a=0;
        a = (int)arg;
        int len,i;
        char *c1 = "Hello world\n";
        char *c2;
        len = strlen(c1);
        int td = pthread_self();
        pthread_detach(pthread_self());
        c2 = c1;
        while(1){

           pthread_mutex_lock(&mutex);
           for(i=0;i<len;i++){
                  fputc(*c1,fp);
                  fflush(fp);
                  c1++;
                  usleep(10000);
           }
           pthread_mutex_unlock(&mutex);
           c1 = c2;
           sleep(1);
        }
}

void *write2(void* arg){
        int a=0;
        a = (int)arg;
        int len,i;
        char *c1 = "How are your\n";
        char *c2;
        c2 = c1;
        len = strlen(c1);
        int td = pthread_self();
        pthread_detach(pthread_self());
        while(1){

           pthread_mutex_lock(&mutex);
           for(i=0;i<len;i++){
                  fputc(*c1,fp);
                  fflush(fp);
                  c1++;
                  usleep(10000);
           }
           pthread_mutex_unlock(&mutex);
           c1 = c2;
           sleep(1);
        }

}

int main(){

        int re,i=0;
        pthread_t tid1,tid2;

        fp = fopen("1.txt","w");
        if(!fp){
                perror("fopen");
                return -1;
        }
        pthread_mutex_init(&mutex,NULL);

        re = pthread_create(&tid1, NULL,write1, (void *)i);
        pthread_detach(tid1);
        if(re!=0){
                printf("pthread_create:%s\n",strerror(re));
                exit(0);
        }
        re = pthread_create(&tid2, NULL,write2, (void *)i);
        pthread_detach(tid2);
        if(re!=0){
                printf("pthread_create:%s\n",strerror(re));
                exit(0);
        }


        while(1){
                sleep(1);
        }

}

写在最后

今天讲完了线程相关的内容,这部分内容比较枯燥,建议跟着我的示例代码敲一敲,然后之前我也写过相关的内容建议参照着一起看【C语言有什么用?②】制作一个多线程词频统计工具所有文件我都放在了gitee哦,需要自取,我尽量一天一更,大家和我一起变强呀!明天开始进入进程间通信专题!最后三连即可提高学习效率!!!


另外我在更新的就是算法笔记的一些例题笔记,这个系列是用于提高我的算法能力,如果有兴趣对算法领域感兴趣找不到合适的入门文章也可以追更,如果我更新的太慢了请大家点赞收藏,一键三连才能更有更新的动力呀0.0

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

XingleiGao

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值