1.多线程编程

Posted 衾许°

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了1.多线程编程相关的知识,希望对你有一定的参考价值。

多线程编程的深入较为复杂,在这里主要是通过全志的5个例程,给自己写一些关于多线程编程的API的笔记

目录

pthread1.c

 pthread2.c

pthread3.c

pthread4.c

 pthread5.c


pthread1.c

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


static void *my_thread_func (void *data)

	while (1)
	
		sleep(1);
	



int main(int argc, char **argv)

	pthread_t tid;
	int ret;
	
	/* 1. 创建"接收线程" */
	ret = pthread_create(&tid, NULL, my_thread_func, NULL);
	if (ret)
	
		printf("pthread_create err!\\n");
		return -1;
	


	/* 2. 主线程读取标准输入, 发给"接收线程" */
	while (1)
	
		sleep(1);
	
	return 0;


第一个例程完成的事情不多,主要在main函数内创建了一个新的线程,如果创建失败则打印”pthread_create err!“

 第一个例程主要用到的API如下:

#include <pthread.h>

/*
函数名:pthread_create,线程创建函数
功能:创建一个新的线程
返回值:如果线程创建成功则返回0,如果不成功则返回一个非0的错误码
*/

int pthread_create(pthread_t *thread, //指向保存线程ID的pthread_t结构
                   const pthread_attr_t *attr, //表示一个封装了线程的各种属性对象,如果为NULL,则使新线程具有默认的属性
                   void *(*start_routine) (void *), //线程开始执行时调用的函数的名字 
                   void *arg); //传递给*(*start_routine)函数的参数


//线程函数默认如下格式:
static void *my_thread_func (void *data)

	while (1);

 编译例程1:

gcc -o pthread pthread1.c -lpthread

 pthread2.c

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

static char g_buf[1000];
static int g_hasData = 0;
static void *my_thread_func (void *data)

	while (1)
	
		//sleep(1);
		/* 等待通知 */
		while (g_hasData == 0);

		/* 打印 */
		printf("recv: %s\\n", g_buf);
		g_hasData = 0;
	

	return NULL;



int main(int argc, char **argv)

	pthread_t tid;
	int ret;
	
	/* 1. 创建"接收线程" */
	ret = pthread_create(&tid, NULL, my_thread_func, NULL);
	if (ret)
	
		printf("pthread_create err!\\n");
		return -1;
	


	/* 2. 主线程读取标准输入, 发给"接收线程" */
	while (1)
	
		fgets(g_buf, 1000, stdin);

		/* 通知接收线程 */
		g_hasData = 1;
	
	return 0;


第二个例程增加了两个全局变量:

g_buf[1000];    //用于存储用户输入的数据流
g_hasData = 0;  //数据流接收标志位,当主线程接受数据流完毕置1,副线程打印结束后置0

主要是实现了主线程将用户输入的数据流接收到g_buf中再由副线程打印出来的一个结果,例程2是基于例程1的基础上完善了主线程与副线程的功能,然而在这里有一个问题:当用户不进行输入时,副线程将会一直等待输入,CPU占用拉满.

while (g_hasData == 0);

第二个例程主要用到的API如下:

#include <stdio.h>
/*
函数名:fgets,数据流读取函数
功能:在stream数据流中读取size大小的数据存储到*s字符串中
返回值:返回*s字符串的第一个字符地址
*/
char *fgets(char *s, int size, FILE *stream);

pthread3.c

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

static char g_buf[1000];
static sem_t g_sem;
static void *my_thread_func (void *data)

	while (1)
	
		//sleep(1);
		/* 等待通知 */
		//while (g_hasData == 0);
		sem_wait(&g_sem);

		/* 打印 */
		printf("recv: %s\\n", g_buf);
	

	return NULL;



int main(int argc, char **argv)

	pthread_t tid;
	int ret;

	sem_init(&g_sem, 0, 0);
	
	/* 1. 创建"接收线程" */
	ret = pthread_create(&tid, NULL, my_thread_func, NULL);
	if (ret)
	
		printf("pthread_create err!\\n");
		return -1;
	


	/* 2. 主线程读取标准输入, 发给"接收线程" */
	while (1)
	
		fgets(g_buf, 1000, stdin);

		/* 通知接收线程 */
		sem_post(&g_sem);
	
	return 0;


第三个例程主要是为了解决第二个例程中副例程等待用户输入的数据流而在while函数内卡住导致CPU占用率拉满的问题而引入了信号量的概念,这个概念主要是出现在计算机操作系统中:如果信号量的S值大于0,wait就将其减1,如果信号量值S等于0,wait就将调用线程挂起。而signal操作的作用是解除指定的信号量对某线程的阻塞,使其从wait中返回,如果没有线程阻塞,则让信号量值S加1

在第三个例程中,引入了信号量变量

static sem_t g_sem;

在主线程中对该信号量进行了初始化,初始值S为0,副线程在创建后,同步于主线程进行运行,在副线程中,当运行到sem_wait(&g_sem);时,因初始值S为0,将副线程挂起,RL寄存器存储printf("recv: %s\\n", g_buf);语句的地址,副线程不再运行,直到主线程中fgets(g_buf, 1000, stdin);接收到用户数据流,才进行sem_post(&g_sem);操作,解除副线程的挂起,RL中printf("recv: %s\\n", g_buf);语句的地址弹出,继续执行printf操作。

但是在这个第三个例程中还是有一点问题:这两个语句分别存在于副线程以及主线程中,当副线程对g_buf中的数据进行打印时(假设数据很长,打印时间也很长),主线程在副线程打印过程中又接收到了用户打印的数据流,那数据就会乱套,可能出现新数据覆盖旧数据打印的情况

printf("recv: %s\\n", g_buf);
fgets(g_buf, 1000, stdin);

第三个例程主要用到的API如下:

#include <semaphore.h>

static sem_t g_sem;//使用信号量需要对信号量进行定义

/*
函数名:sem_init,信号量初始化
功能:对信号量进行初始化
返回值:初始化成功返回0,失败返回-1
*/
int sem_init(sem_t *sem, //指向要初始化的信号量
             int pshared, //信号量的共享范围,如果pshared为0,那么该信号量只能由初始化这个信号量的进程中的线程使用,如果非0,则任何可访问到这个信号量的进程都可以使用这个信号量
             unsigned int value); //value为信号量的初始值
#include <semaphore.h>

/*
函数名:sem_post,signal操作函数
功能:实现对指定信号量的signal操作
返回值:成功返回0,失败返回-1
*/
int sem_post(sem_t *sem); //可设置要进行signal操作的sem变量
#include <semaphore.h>

/*
函数名:sem_wait,wait操作函数
功能:实现对指定信号量的wait操作
返回值:成功返回0,失败返回-1
*/
int sem_wait(sem_t *sem); //可设置要进行wait操作的sem变量

pthread4.c

#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;
static pthread_mutex_t g_tMutex  = PTHREAD_MUTEX_INITIALIZER;

static void *my_thread_func (void *data)

	while (1)
	
		//sleep(1);
		/* 等待通知 */
		//while (g_hasData == 0);
		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];

	sem_init(&g_sem, 0, 0);
	
	/* 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);    //为了在主线程完成了sem_post(&g_sem);语句后,不立即对互斥量进行抢占,使用一个buf来临时存储g_buf的数据,用户在副线程打印数据的过程中,无论输入什么都会到buf中
		pthread_mutex_lock(&g_tMutex);
		memcpy(g_buf, buf, 1000);
		pthread_mutex_unlock(&g_tMutex);

		/* 通知接收线程 */
		sem_post(&g_sem);
	
	return 0;


为了解决g_buf这个变量在两个线程中抢来抢去的问题,引入了互斥变量的概念,解决例程3的问题

第四个例程主要用到的API如下:

static pthread_mutex_t g_tMutex  = PTHREAD_MUTEX_INITIALIZER;//表示静态分配的互斥变量
pthread_mutex_lock(&g_tMutex);\\\\获取互斥量锁
pthread_mutex_unlock(&g_tMutex);\\\\解锁

 pthread5.c

#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;
static pthread_cond_t  g_tConVar = PTHREAD_COND_INITIALIZER;

static void *my_thread_func (void *data)

	while (1)
	
		//sleep(1);
		/* 等待通知 */
		//while (g_hasData == 0);
		pthread_mutex_lock(&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_mutex_unlock(&g_tMutex);
	
	return 0;


 第五个例程是为了完善前四个例程引入了条件互斥变量的概念:在这里以条件的形式将信号量代替,即API版的signal操作,原理是差不多的,就不多赘述

static pthread_cond_t  g_tConVar = PTHREAD_COND_INITIALIZER;
pthread_cond_signal(&g_tConVar); /* 通知接收线程 */

以上是关于1.多线程编程的主要内容,如果未能解决你的问题,请参考以下文章

4.c++语言级别的多线程编程

多线程编程C语言版(附代码示例)

在C语言的多线程编程中一般volatile应该用在啥地方?

c语言多线程pthread的问题

C 语言编程 — pthread 线程操作

c语言怎么创建线程和使用