【linux多线程的基本操作】

linux多线程的基本操作


前言

本章内容分享linux多线程的一些基本使用方法,和线程中的互斥锁以及条件控制实现线程同步。
提示:以下是本篇文章正文内容,下面案例可供参考

一、多线程的概述

线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。线程包含了表示进程内执行环境必须的信息,其中包括进程中表示线程的线程ID、一组寄存器值、栈、调度优先级和策略、信号屏蔽字、errno常量以及线程私有数据。进程的所有信息对该进程的所有线程都是共享的,包括可执行的程序文本、程序的全局内存和堆内存、栈以及文件描述符。在Unix和类Unix操作系统中线程也被称为轻量级进程(lightweight processes),但轻量级进程更多指的是内核线程(kernel thread),而把用户线程(user thread)称为线程。

线程中常使用的相关API
在这里插入图片描述

"进程——资源分配的最小单位,线程——程序执行的最小单位"

二、多线程的创建等待与退出

下面用一些简单的小demo来演示如何对API的使用,相关的函数原型参数 我们还是通过man 手册来查看

//1.pthread_create 线程创建
 int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                    void *(*start_routine) (void *), void *arg);
 参数1为我们创建的线程,参数2一般为NULL,需要传参时写入参数3中多个值时可以传结构体
 创建成功返回0 失败返回一个错误的值
//2.pthread_join //线程等待
 int pthread_join(pthread_t thread, void **retval);
参数1传入我们的线程值,参数2如何有传入值的话就填我们想要的值,没有用到的话填NULL即可
//3.pthread_exit //线程退出
 void pthread_exit(void *retval);

2.1 实验演示

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


void *func(void *arg)
{
    
    
	//static int ret = -1;  注意加static 不然运行的结果是个随机值
	static char *p = "t1 is out";
	printf("t1:%ld thread is cretae\n",pthread_self());
	printf("t1:param = %d\n",*((int *)arg));
	//pthread_exit((void *)&ret);
	pthread_exit((void *)p); //线程退出将P字符串的值打印
}

int main(void)
{
    
    

	pthread_t t1; 
	int param = 10;
	int ret = 0;
	//int *pret = NULL;
	char *pret = NULL;

	//创建线程
	ret = pthread_create(&t1,NULL,func,(void *)&param);
	if(ret == 0)
	{
    
    
		printf("main:pthread create success\n");
	}
	printf("main:%ld\n",pthread_self());
	pthread_join(t1,(void **)&pret);
	//printf("main:t1 quit ret = %d\n",*pret);
	printf("main:%s\n",pret);
	return 0;
}

运行结果
在这里插入图片描述

三、线程共享内存空间

线程可以说是属于进程的。一个进程中可以有多个线程,并且线程所拥有的内存资源都是属于该进程的资源,当有一个线程死掉的时候这个进程的会结束,下面在以一个demo演示多线程间的共享内存空间。

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

int g_data = 0;

void *func(void *arg)
{
    
    	
	printf("t1:%ld thread is cretae\n",pthread_self());
	printf("t1:param = %d\n",*((int *)arg));
	while(1)
	{
    
    
		printf("t1 :g_data = %d\n",g_data++);
		sleep(1);
	}
}


void *func2(void *arg)
{
    
    	
	printf("t2:%ld thread is cretae\n",pthread_self());
	printf("t2:param = %d\n",*((int *)arg));
	while(1)
	{
    
    
		printf("t2 :g_data = %d\n",g_data++);
		sleep(1);
	}
}


int main(void)
{
    
    
	pthread_t t1;
	pthread_t t2;
	int param = 10;
	int ret = 0;

	//创建线程
	ret = pthread_create(&t1,NULL,func,(void *)&param);
	if(ret == 0)
	{
    
    
		printf("main:t1 pthread create success\n");
	}

	ret = pthread_create(&t2,NULL,func2,(void *)&param);
	if(ret == 0)
	{
    
    
		printf("main:t2 pthread create success\n");
	}

	while(1)
	{
    
    
		printf("main:g_data = %d\n",g_data++);
		sleep(1);
	}
	printf("main:%ld\n",pthread_self());

	pthread_join(t1,NULL);
	pthread_join(t2,NULL);
	return 0;
}

运行结果
在这里插入图片描述

结果所打印的g_data的值都是连续的,所以说我们定义的g_data这个全局变量在该多线程中他们都是共享该空间的

四、线程同步之互斥加锁解锁

在多线程为什么会引进锁这个概念呢?
我们在使用多线程的时候他们是各自运行的,很多时候我们需要阻塞摸一个线程等待其他线程运行后再运行该线程,这时候解决方式就是上锁,将阻塞的线程先上锁,达到我们的条件时在解锁,这样就可以实现每次只有一个线程运行。
互斥变量用pthread_mutex_t数据类型表示。在使用互斥变量前必须对它进行初始化,可以把它置为常量PTHREAD_MUTEX_INITIALIZER(只对静态分配的互斥量),也可以通过调用pthread_mutex_init函数进行初始化。如果动态地分配互斥量(例如通过调用malloc函数),释放内存之前使用pthead_mutex_destroy将其销毁。

函数原型
//初始化锁
int pthread_mutex_init(pthread_mutex_t *restrict *mutex, const pthread_mutexattr_t *restrict attr);
//销毁所
int pthread_mutex_destroy(pthread_mutex_t *mutex);
//上锁
int pthread_mutex_lock(pthread_mutex_t *mutex);
//解锁
int pthread_mutex_unlock(pthread_mutex_t *mutex);
// 返回:若成功返回0,否则返回错误编号

4.1 互斥锁演示一

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

//如何造成死锁?多把锁同时使用时 
int g_data = 0;
pthread_mutex_t mutex;

//被锁上的代码等该函数内代码全部执行完毕之后才执行下一个线程的
void *func(void *arg)
{
    
    	
	pthread_mutex_lock(&mutex);
	printf("t1:%ld thread is cretae\n",pthread_self());
	printf("t1:param = %d\n",*((int *)arg));
	pthread_mutex_unlock(&mutex);
	
}


void *func2(void *arg)
{
    
    	
	pthread_mutex_lock(&mutex);
	printf("t2:%ld thread is cretae\n",pthread_self());
	printf("t2:param = %d\n",*((int *)arg));
	pthread_mutex_unlock(&mutex);
}


int main(void)
{
    
    

	pthread_t t1;
	pthread_t t2;
	int param = 10;
	int ret = 0;

	pthread_mutex_init(&mutex,NULL); //初始化锁
	//创建线程
	ret = pthread_create(&t1,NULL,func,(void *)&param);
	if(ret == 0)
	{
    
    
		printf("main:t1 pthread create success\n");
	}

	ret = pthread_create(&t2,NULL,func2,(void *)&param);
	if(ret == 0)
	{
    
    
		printf("main:t2 pthread create success\n");
	}

	printf("main:%ld\n",pthread_self());

	pthread_join(t1,NULL);
	pthread_join(t2,NULL);
	pthread_mutex_destroy(&mutex);
	return 0;
}

运行结果
在这里插入图片描述

从结果我们可以看出程序运行时要等每一个线程运行完成后才会执行下一个线程里面的内容

4.2 互斥锁演示二(死锁)

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

//如何造成死锁?多把锁同时使用时 
int g_data = 0;
pthread_mutex_t mutex;
pthread_mutex_t mutex2;

//被锁上的代码等该函数内代码全部执行完毕之后才执行下一个线程的
void *func(void *arg)
{
    
    	
	pthread_mutex_lock(&mutex);
	sleep(1);
	pthread_mutex_lock(&mutex2);
	printf("t1:%ld thread is cretae\n",pthread_self());
	printf("t1:param = %d\n",*((int *)arg));
	pthread_mutex_unlock(&mutex);
	pthread_mutex_unlock(&mutex2);
	
}


void *func2(void *arg)
{
    
    	
	pthread_mutex_lock(&mutex2);
	sleep(1);
	pthread_mutex_lock(&mutex);
	printf("t2:%ld thread is cretae\n",pthread_self());
	printf("t2:param = %d\n",*((int *)arg));
	pthread_mutex_unlock(&mutex2);
	pthread_mutex_unlock(&mutex);
}


int main(void)
{
    
    

	pthread_t t1;
	pthread_t t2;
	int param = 10;
	int ret = 0;

	pthread_mutex_init(&mutex,NULL); //初始化锁
	pthread_mutex_init(&mutex2,NULL); //初始化锁
	//创建线程
	ret = pthread_create(&t1,NULL,func,(void *)&param);
	if(ret == 0)
	{
    
    
		//printf("main:t1 pthread create success\n");
	}

	ret = pthread_create(&t2,NULL,func2,(void *)&param);
	if(ret == 0)
	{
    
    
		//printf("main:t2 pthread create success\n");
	}

	printf("main:%ld\n",pthread_self());

	pthread_join(t1,NULL);
	pthread_join(t2,NULL);
	pthread_mutex_destroy(&mutex);
	pthread_mutex_destroy(&mutex2);
	return 0;
}

运行结果
在这里插入图片描述

五、线程条件控制实现线程同步

函数原型
int pthread_cond_init(pthread_cond_t *restrict *cond, const pthread_condattr_t *restrict attr);
int pthread_cond_destroy(pthread_cond_t *cond);
// 返回:若成功返回0,否则返回错误编号
int pthread_cond_wait(pthread_cond_t *restrict *cond, pthread_mutex_t *restrict mutex);
int pthread_cond_signal(pthread_cond_t *cond);

下面实验基于上面进行修改,当线程func2中g_data的值每次打印三次时在通过线程func中运行一次。

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

//如何造成死锁?多把锁同时使用时 
int g_data = 0;
pthread_mutex_t mutex;
pthread_cond_t cond;

//被锁上的代码等该函数内代码全部执行完毕之后才执行下一个线程的
void *func(void *arg)
{
    
    	
	printf("t1:%ld thread is cretae\n",pthread_self());
	printf("t1:param = %d\n",*((int *)arg));
	
	while(1)
	{
    
    
		pthread_cond_wait(&cond,&mutex);
		printf("------------------t1 run-------------------\n");
		printf("t1:%d\n",g_data);
		g_data = 0;
		sleep(1);	
	}
	
}


void *func2(void *arg)
{
    
    	
	printf("t2:%ld thread is cretae\n",pthread_self());
	printf("t2:param = %d\n",*((int *)arg));
	while(1)
	{
    
    
		printf("t2:%d\n",g_data);
		pthread_mutex_lock(&mutex);
		g_data++;
		if(g_data == 3)
		{
    
    
			pthread_cond_signal(&cond);
		}
		pthread_mutex_unlock(&mutex);
		sleep(1);
	}
}


int main(void)
{
    
    

	pthread_t t1;
	pthread_t t2;
	int param = 10;
	int ret = 0;

	pthread_mutex_init(&mutex,NULL); //初始化锁
	pthread_cond_init(&cond,NULL);
	//创建线程
	ret = pthread_create(&t1,NULL,func,(void *)&param);
	if(ret == 0)
	{
    
    
		//printf("main:t1 pthread create success\n");
	}

	ret = pthread_create(&t2,NULL,func2,(void *)&param);
	if(ret == 0)
	{
    
    
		//printf("main:t2 pthread create success\n");
	}

	//printf("main:%ld\n",pthread_self());

	pthread_join(t1,NULL);
	pthread_join(t2,NULL);
	pthread_mutex_destroy(&mutex);
	pthread_cond_destroy(&cond);
	return 0;
}

运行结果
在这里插入图片描述


总结

以上就是本章分享和记录学习的内容,通过此次学习也掌握了线程的基本使用方式,线程和进程的区别以及二者之间的联系。加油

猜你喜欢

转载自blog.csdn.net/boybs/article/details/123065505