当前位置: 首页 > news >正文

嵌入式系统多线程学习笔记

1) 标题最简单的多线程程序,不存在互斥


#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>     /*Unix标准函数定义*/
void * thread1(void *arg)
{
   while(1)
   {
      printf("hello world!\n");
      sleep(1);
    }
}
int main()
{
   pthread_t id;
   if(pthread_create(&id,NULL,thread1,NULL)!=0)
   {
      perror("pthread create error!\n");
      exit(1);
   }
   while(1)
   {
      
   }
   //pause(); 
   return 0;
}

注意编译的时候必须增加 –lpthread 选项,否则是不能编译通过的,因为在linux中默认编译时不引用多线程的库的,这样才能在编译的时候调用多线程的库。
main:main.o
$(CC) main.o -o thread -lpthread
main.o:main.c
$(CC) -c main.c -o main.o -lpthread
clean:
$(RM) *.o thread

2) 使用信号量进行多线程编程 (适用于无规则,不控制,仅保证不冲突即可,互不干扰)

#include <pthread.h>
int pthread_mutex_init(phtread_mutex_t *mutex, const pthread_mutexattr_t *restrict attr);
该函数初始化一个互斥量,第一个参数 *mutex是改互斥量指针,第二个参数 *restrict attr为控制互斥量的属性,一般为NULL。当函数成功后会返回0,代表初始化互斥量成功。
  初始化互斥量也可以调用宏来快速初始化,代码如下:
pthread_mutex_t mutex = PTHREAD_MUTEX_INITALIZER;
互斥量加锁/解锁
#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
成功:返回0
lock函数与unlock函数分别为加锁解锁函数,只需要传入已经初始化好的pthread_mutex_t互斥量指针。成功后会返回0。
  当某一个线程获得了执行权后,执行lock函数,一旦加锁成功后,其余线程遇到lock函数时候会发生阻塞,直至获取资源的线程执行unlock函数后。unlock函数会唤醒其他正在等待互斥量的线程。
  特别注意的是,当获取lock之后,必须在逻辑处理结束后执行unlock,否则会发生死锁现象!导致其余线程一直处于阻塞状态,无法执行下去。在使用互斥量的时候,尤其要注意使用pthread_cancel函数,防止发生死锁现象!
int pthread_cancel(pthread_t thread);函数用于线程被动退出,该函数传入一个tid号,会强制退出该tid所指向的线程,若成功执行会返回0
线程的创建
#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);
   *thread:用来保存新建线程的线程号
   *attr:表示线程的属性,一般传入NULL表示默认属性
   *(start_routine) (void ):一个函数指针,就是线程执行的函数。这个函数返回值为void,形参为void
   *arg:表示为向线程处理函数传入的参数,若不传入,可用NULL填充


#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<pthread.h>	/*信号量操作要包含的库*/
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<semaphore.h>   /*使用信号量PV要包含的库 */
struct  message
{
    int fd;
};
sem_t sem[1];
void * thread1(void *arg)
{
    struct message msg =  *((struct message * )arg);
    int fd= msg.fd;
    while(1)
{
/* */
        sem_wait(&sem[0]);
        printf("write 111\n");
        write(fd,"111\n",4);
        sem_post(&sem[0]);
        sleep(1);
    }
}
void * thread2(void *arg)
{
     struct message msg =  *((struct message * )arg);
    int fd= msg.fd;
    while(1)
    {
        sem_wait(&sem[0]);
         printf("write 222\n");
        write(fd,"222\n",4);
        sem_post(&sem[0]);
        sleep(1);
    }
}
void *thread3(void *arg)
{
     struct message msg =  *((struct message * )arg);
    int fd= msg.fd;
    while(1)
    {
        sem_wait(&sem[0]);
        printf("write 333\n");
        write(fd,"333\n",4);
        sem_post(&sem[0]);
        sleep(1);
    }
}
int main()
{
    pthread_t id1;
    pthread_t id2;
    pthread_t id3;
    struct message msg;
    int fd;
    fd=open("./a.txt",O_RDWR | O_CREAT | O_APPEND,0644);
    if(fd<0)
    {
        perror("open file error!");
        exit(1);
    }
msg.fd=fd;
/*初始化一个信号量
第一个参数*sem传入sem_t类型指针
第二个参数pshared传入0代表线程控制,否则为进程控制
 第三个参数value表示信号量的初始值,0代表阻塞,1代表运行
 待初始化结束信号量后,若执行成功会返回0
*/
    sem_init(&sem[0],0,1);
     
    if(pthread_create(&id1,NULL,thread1,(void *)(&msg)) != 0)
    {
        perror("thread create error!\n");
        exit(1);
    }
    if(pthread_create(&id2,NULL,thread2,(void *)(&msg)) != 0)
    {
        perror("thread create error!\n");
        exit(1);
    }
   if(pthread_create(&id3,NULL,thread3,(void *)(&msg)) != 0)
    {
        perror("thread create error!\n");
        exit(1);
    }
    pthread_join(id1,NULL);
    pthread_join(id2,NULL);
    pthread_join(id3,NULL);
    return 0;
}

3) 多线程互斥操作(适合于主从线程,从线程可有主线程控制的情况,使用信号量控制)

这个实际上和2)差不多就是指定一个主循环运行一段代码,相当于一个线程,然后在主循环中给出从线程运行的通知,让从线程执行。
https://blog.csdn.net/weixin_43444989/article/details/123106933
初始化互斥量
#include <pthread.h>
int pthread_mutex_init(phtread_mutex_t *mutex, const pthread_mutexattr_t *restrict attr);
  该函数初始化一个互斥量,第一个参数 *mutex是改互斥量指针,第二个参数 *restrict attr为控制互斥量的属性,一般为NULL。当函数成功后会返回0,代表初始化互斥量成功。
  初始化互斥量也可以调用宏来快速初始化,代码如下:
pthread_mutex_t mutex = PTHREAD_MUTEX_INITALIZER;
或者
pthread_mutex_t mut
pthread_mutex_init(&mut,NULL);
互斥量加锁/解锁
#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
成功:返回0
  lock函数与unlock函数分别为加锁解锁函数,只需要传入已经初始化好的pthread_mutex_t互斥量指针。成功后会返回0。
  当某一个线程获得了执行权后,执行lock函数,一旦加锁成功后,其余线程遇到lock函数时候会发生阻塞,直至获取资源的线程执行unlock函数后。unlock函数会唤醒其他正在等待互斥量的线程。
  特别注意的是,当获取lock之后,必须在逻辑处理结束后执行unlock,否则会发生死锁现象!导致其余线程一直处于阻塞状态,无法执行下去。在使用互斥量的时候,尤其要注意使用pthread_cancel函数,防止发生死锁现象!
int pthread_cancel(pthread_t thread);函数用于线程被动退出,该函数传入一个tid号,会强制退出该tid所指向的线程,若成功执行会返回0
线程的创建
#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);
   *thread:用来保存新建线程的线程号
   *attr:表示线程的属性,一般传入NULL表示默认属性
   *(start_routine) (void ):一个函数指针,就是线程执行的函数。这个函数返回值为void,形参为void
   *arg:表示为向线程处理函数传入的参数,若不传入,可用NULL填充
程序功能:从主线程中接收键盘输入内容保存在全局变量中,在子线程中将变量中的内容输出

#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <semaphore.h>
#include <string.h>
static char g_buf[1000];//用来保存键盘输入内容的全局变量
static sem_t g_sem;
//也可以使用 pthread_mutex_init(&g_tMutex, NULL); 来初始化互斥量
static pthread_mutex_t g_tMutex  = PTHREAD_MUTEX_INITIALIZER;
static void *my_thread_func (void *data)
{
	while (1)
	{
		/* 等待通知 */
		sem_wait(&g_sem);
		/* 打印 */
		pthread_mutex_lock(&g_tMutex);
		printf("recv: %s\n", g_buf);
		pthread_mutex_unlock(&g_tMutex);
	}
	return NULL;
}
int main(int argc, char **argv)
{
	pthread_t tid;
	int ret;
	char buf[1000];
	//g_sem:sem_t类型变量 0:线程控制 0:信号量的初始值,0代表阻塞 
	sem_init(&g_sem, 0, 0);
	
	/* 1. 创建"接收线程" */
	//tid:保存新建的线程号 NULL:默认属性 my_thread_func:函数指针 NULL:无参数
	ret = pthread_create(&tid, NULL, my_thread_func, NULL);
	if (ret)
	{
		printf("pthread_create err!\n");
		return -1;
	}
	/* 2. 主线程读取标准输入, 发给"接收线程" */
	while (1)
	{
		//获取标准输入存入buf
		fgets(buf, 1000, stdin);
		//加锁成功会返回0,一旦加锁成功其他线程想给同一个互斥量加锁会发生阻塞
		pthread_mutex_lock(&g_tMutex);
		memcpy(g_buf, buf, 1000);
		//解锁成功会返回0,一旦解锁成功,函数会唤醒其他正在等待互斥量的线程
		pthread_mutex_unlock(&g_tMutex);
		/* 通知接收线程 */
		sem_post(&g_sem);
	}
	return 0;
}

4) 使用条件变量控制线程

创建和销毁条件变量
#include <pthread.h>
// 初始化条件变量
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr);//cond_attr通常为NULL
// 销毁条件变量
int pthread_cond_destroy(pthread_cond_t *cond);
这些函数成功时都返回0
等待条件变量
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t mutex);
  这需要结合互斥量一起使用,示例代码如下:
pthread_mutex_lock(&g_tMutex);
pthread_cond_wait(&g_tConVar, &g_tMutex); // 如果条件不满足则,会unlock g_tMutex
// 条件满足后被唤醒,会lock g_tMutex
/
操作临界资源 */
pthread_mutex_unlock(&g_tMutex);
  pthread_cond_wait总和一个互斥锁结合使用。在调用pthread_cond_wait前要先获取锁,因为pthread_cond_wait函数执行时先自动释放指定的锁,然后等待条件变量的变化,在函数调用返回之前,自动将指定的互斥量重新锁住。
通知条件变量
int pthread_cond_signal(pthread_cond_t *cond);
  pthread_cond_signal函数只会唤醒一个等待cond条件变量的线程,示例代码如下:
pthread_cond_signal(&g_tConVar);
  pthread_cond_signal通过条件变量cond发送消息,若多个消息在等待,它只唤醒一个
  调用pthread_cond_signal后要立刻释放互斥锁,因为pthread_cond_wait的最后一步是要将指定的互斥量重新锁住,如果pthread_cond_signal之后没有释放互斥锁,pthread_cond_wait仍然要阻塞。

#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <semaphore.h>
#include <string.h>
static char g_buf[1000];
static pthread_mutex_t g_tMutex  = PTHREAD_MUTEX_INITIALIZER;
//也可以使用pthread_cond_init(g_tConVar,NULL);来初始化条件变量
static pthread_cond_t  g_tConVar = PTHREAD_COND_INITIALIZER;
static void *my_thread_func (void *data)
{
	while (1)
	{
		pthread_mutex_lock(&g_tMutex);
		/*
			pthread_cond_wait函数先释放g_tMutex
			等待条件变量g_tConVar变化后在函数返回之前重新锁定g_tMutex
		*/
		pthread_cond_wait(&g_tConVar, &g_tMutex);	
		/* 打印 */
		printf("recv: %s\n", g_buf);
		pthread_mutex_unlock(&g_tMutex);
	}
	return NULL;
}
int main(int argc, char **argv)
{
	pthread_t tid;
	int ret;
	char buf[1000];
	
	/* 1. 创建"接收线程" */
	ret = pthread_create(&tid, NULL, my_thread_func, NULL);
	if (ret)
	{
		printf("pthread_create err!\n");
		return -1;
	}
	/* 2. 主线程读取标准输入, 发给"接收线程" */
	while (1)
	{
		fgets(buf, 1000, stdin);
		pthread_mutex_lock(&g_tMutex);
		memcpy(g_buf, buf, 1000);
		pthread_cond_signal(&g_tConVar); /* 通知接收线程 */
		//调用pthread_cond_signal后要立刻释放互斥锁
		pthread_mutex_unlock(&g_tMutex);
	}
	return 0;
}

5) 向线程传递参数-整型值

pthread_create()的最后一个参数的为void类型的数据,表示可以向线程传递一个void数据类型的参数,线程的回调函数中可以获取该参数,下面的例子举例了如何向线程传入变量地址与变量值。

#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
void *fun1(void *arg)
{
	//(int *)arg:	a的值
	//arg			:a的地址
	printf("%s:arg = %d Addr = %p\n",__FUNCTION__,*(int *)arg,arg);
}
void *fun2(void *arg)
{
	//(int)(long)arg:a的值
	//arg			:输出是16进制a的值
	printf("%s:arg = %d Addr = %p\n",__FUNCTION__,(int)(long)arg,arg);
}
int main()
{
	pthread_t tid1,tid2;
	int a = 50;
	//传递变量a的地址
	int ret = pthread_create(&tid1,NULL,fun1,(void *)&a);
	if(ret != 0){
		perror("pthread_create");
		return -1;
	}
	//传递变量a的值,针对不同位数机器,指针对其字数不同,需要int转化为long再转指针,否则可能会发生警告
	ret = pthread_create(&tid2,NULL,fun2,(void *)(long)a);
	if(ret != 0){
		perror("pthread_create");
		return -1;
	}
	sleep(1);
	printf("%s:a = %d Add = %p \n",__FUNCTION__,a,&a);
	return 0;
}

6) 传递结构体参数给线程

#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
struct Stu{
	int Id;
	char Name[32];
	float Mark;
};
void *fun1(void *arg)
{
	struct Stu *tmp = (struct Stu *)arg;
	printf("%s:Id = %d Name = %s Mark = %.2f\n",__FUNCTION__,tmp->Id,tmp->Name,tmp->Mark);
	
}
int main()
{
	pthread_t tid1,tid2;
	struct Stu stu;
	stu.Id = 10000;
	strcpy(stu.Name,"ZhangSan");
	stu.Mark = 94.6;
	int ret = pthread_create(&tid1,NULL,fun1,(void *)&stu);
	if(ret != 0){
		perror("pthread_create");
		return -1;
	}
	printf("%s:Id = %d Name = %s Mark = %.2f\n",__FUNCTION__,stu.Id,stu.Name,stu.Mark);
	sleep(1);
	return 0;
}

7) 线程的退出与回收

线程的退出情况有三种:第一种是进程结束,进程中所有的线程也会随之结束。第二种是通过函数pthread_exit来主动的退出线程。第三种被其他线程调用pthread_cancel来被动退出。
线程主动退出
#include <pthread.h>
void pthread_exit(void retval);
  pthread_exit函数为线程退出函数,在退出时候可以传递一个void
类型的数据带给主线程,若选择不传出数据,可将参数填充为NULL。
线程被动退出
线程被动退出,其他线程使用该函数让另一个线程退出
#include <pthread.h>
int pthread_cancel(pthread_t thread);
成功:返回0
  该函数传入一个tid号,会强制退出该tid所指向的线程,若成功执行会返回0。
线程资源回收(阻塞方式)
#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);
  该函数为线程回收函数,默认状态为阻塞状态,直到成功回收线程后才返回。第一个参数为要回收线程的tid号,第二个参数为线程回收后接受线程传出的数据。
线程资源回收(非阻塞方式)
#define _GNU_SOURCE
#include <pthread.h>
int pthread_tryjoin_np(pthread_t thread, void **retval);
  该函数为非阻塞模式回收函数,通过返回值判断是否回收掉线程,成功回收则返回0,其余参数与pthread_join一致。
程序实例
程序功能:向子线程中传入变量在子线程中自加后子线程主动退出将自加后变量返回给主线程,主线程以阻塞方式回收子线程资源

#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
void *fun1(void *arg)
{
	static int tmp = 0;//必须要static修饰,否则pthread_join无法获取到正确值
	tmp = *(int *)arg;
	tmp+=100;
	printf("%s:Addr = %p tmp = %d\n",__FUNCTION__,&tmp,tmp);
	//线程主动退出
	pthread_exit((void *)&tmp);
}
int main()
{
	pthread_t tid1;
	int a = 50;
	void *Tmp = NULL;//用来接收线程回收后传递出来的数据
	int ret = pthread_create(&tid1,NULL,fun1,(void *)&a);
	if(ret != 0){
		perror("pthread_create");
		return -1;
	}
	//以阻塞方式完成线程资源回收
	pthread_join(tid1,&Tmp);
	printf("%s:Addr = %p Val = %d\n",__FUNCTION__,Tmp,*(int *)Tmp);
	return 0;
}
使用非阻塞方式回收线程
#define _GNU_SOURCE 
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
void *fun(void *arg)
{
	printf("Pthread:%d Come !\n",(int )(long)arg+1);
	pthread_exit(arg);
}
int main()
{
	int ret,i,flag = 0;
	void *Tmp = NULL;
	pthread_t tid[3];
	for(i = 0;i < 3;i++){//创建3个线程
		ret = pthread_create(&tid[i],NULL,fun,(void *)(long)i);
		if(ret != 0){
			perror("pthread_create");
			return -1;
		}
	}
	while(1){//通过非阻塞方式收回线程,每次成功回收一个线程变量自增,直至3个线程全数回收
		for(i = 0;i <3;i++){
			if(pthread_tryjoin_np(tid[i],&Tmp) == 0){
				printf("Pthread : %d exit !\n",(int )(long )Tmp+1);
				flag++;	
			}
		}
		if(flag >= 3) break;
	}
	return 0;
}

使用被动退出方式在线程1中kill线程2

#define _GNU_SOURCE 
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
void *fun1(void *arg)
{
	printf("Pthread:1 come!\n");
	while(1){
		sleep(1);
	}
}
void *fun2(void *arg)
{
	printf("Pthread:2 come!\n");
	pthread_cancel((pthread_t )(long)arg);//杀死线程1,使之强制退出
	pthread_exit(NULL);
}
int main()
{
	int ret,i,flag = 0;
	void *Tmp = NULL;
	pthread_t tid[2];
	ret = pthread_create(&tid[0],NULL,fun1,NULL);
	if(ret != 0){
		perror("pthread_create");
		return -1;
	}
	sleep(1);
	ret = pthread_create(&tid[1],NULL,fun2,(void *)tid[0]);//传输线程1的线程号
	if(ret != 0){
		perror("pthread_create");
		return -1;
	}
	while(1){//通过非阻塞方式收回线程,每次成功回收一个线程变量自增,直至2个线程全数回收
		for(i = 0;i <2;i++){
			if(pthread_tryjoin_np(tid[i],NULL) == 0){
				printf("Pthread : %d exit !\n",i+1);
				flag++;	
			}
		}
		if(flag >= 2) break;
	}
	return 0;
}

创作不易,欢迎点赞关注. 下次会发布一个232的测试程序,用多线程方式编写.敬请期待.

相关文章:

  • 【DaVinci Developer专题】-44-Software Component软件组件的Multiple Instantiation多次实例化
  • Docker 进阶指南(下)- 使用Docker Compose编排多个容器
  • 走进Prime Time系列 - 走进PT - 01
  • 天龙八部科举答题问题和答案(全4/8)
  • 【聚类算法】带你轻松搞懂K-means聚类(含代码以及详细解释)
  • 【电源专题】案例:为什么芯片支持0.8V的工作电压,但EN却又要高达1.25V?
  • 湖仓一体电商项目(十四):实时任务执行流程
  • 猿创征文|Java中的IO流大家族 (两万字详解)
  • SQL Server 2014安装教程(保姆级图解教程)
  • 大白话理解-微信小程序获取授权
  • Spring常用注解。
  • Spring之拦截器
  • 【知识图谱】Louvain、LPA等5类经典社区发现算法 Python 实战
  • SQL server 2008 r2 安装教程
  • python wechat --- 企业微信机器人API
  • -------------------- 第二讲-------- 第一节------在此给出链表的基本操作
  • 【5+】跨webview多页面 触发事件(二)
  • 【翻译】Mashape是如何管理15000个API和微服务的(三)
  • 【跃迁之路】【585天】程序员高效学习方法论探索系列(实验阶段342-2018.09.13)...
  • 0x05 Python数据分析,Anaconda八斩刀
  • Akka系列(七):Actor持久化之Akka persistence
  • Angular2开发踩坑系列-生产环境编译
  • Bytom交易说明(账户管理模式)
  • Docker 1.12实践:Docker Service、Stack与分布式应用捆绑包
  • ERLANG 网工修炼笔记 ---- UDP
  • GDB 调试 Mysql 实战(三)优先队列排序算法中的行记录长度统计是怎么来的(上)...
  • Java 网络编程(2):UDP 的使用
  • JavaScript实现分页效果
  • nginx 配置多 域名 + 多 https
  • React-Native - 收藏集 - 掘金
  • React中的“虫洞”——Context
  • Web标准制定过程
  • XForms - 更强大的Form
  • 阿里云应用高可用服务公测发布
  • 关于Flux,Vuex,Redux的思考
  • 面试总结JavaScript篇
  • 使用SAX解析XML
  • 思考 CSS 架构
  • 在GitHub多个账号上使用不同的SSH的配置方法
  • 转载:[译] 内容加速黑科技趣谈
  • media数据库操作,可以进行增删改查,实现回收站,隐私照片功能 SharedPreferences存储地址:
  • 阿里云IoT边缘计算助力企业零改造实现远程运维 ...
  • 进程与线程(三)——进程/线程间通信
  • # 达梦数据库知识点
  • #LLM入门|Prompt#1.8_聊天机器人_Chatbot
  • #Z2294. 打印树的直径
  • #传输# #传输数据判断#
  • #在线报价接单​再坚持一下 明天是真的周六.出现货 实单来谈
  • (1)虚拟机的安装与使用,linux系统安装
  • (1/2) 为了理解 UWP 的启动流程,我从零开始创建了一个 UWP 程序
  • (31)对象的克隆
  • (附源码)python房屋租赁管理系统 毕业设计 745613
  • (附源码)springboot教学评价 毕业设计 641310
  • (附源码)ssm考试题库管理系统 毕业设计 069043
  • (三) prometheus + grafana + alertmanager 配置Redis监控