进程线程知识之线程同步

这篇具有很好参考价值的文章主要介绍了进程线程知识之线程同步。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

  • 线程同步原因
  • 互斥锁
  • 信号量
  • 条件变量
  • 读写锁

1、线程同步原因

线程同步是为了对共享资源的访问进行保护,目的是为了解决数据一致性的问题

出现数据一致性问题本质在于进程中的多个线程对共享资源的并发访问(同时访问)。

为确保不会出现对共享资源的并发访问,Linux系统提供了多种实现线程同步的机制,常见的有互斥锁、条件变量、自旋锁以及读写锁等。

2、互斥锁

互斥锁(mutex)又叫做互斥量,在访问共享资源之前对互斥锁进行上锁,在访问完成后释放互斥锁。对互斥锁进行上锁之后,任何其它试图再次对互斥锁进行加锁的线程都会被阻 塞,直到当前线程释放互斥锁。如果释放互斥锁时有一个以上的线程阻塞,那么这些阻塞的线程会被唤醒, 它们都会尝试对互斥锁进行加锁,当有一个线程成功对互斥锁上锁之后,其它线程就不能再次上锁了,只能 再次陷入阻塞,等待下一次解锁。

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

static pthread_mutex_t mutex; 
static int g_count = 0;

static void *new_thread_start(void *arg)
{
	int loops = *((int*)arg);
	int l_count, j;
	
	for(j = 0; j < loops; j++)
	{
		//pthread_mutex_lock(&mutex);    //互斥锁上锁 
		
		while(pthread_mutex_trylock(&mutex));   //以非阻塞方式上锁 
		
		l_count = g_count;
		l_count ++;
		g_count = l_count;
		
		pthread_mutex_unlock(&mutex);   //互斥锁解锁 
	}
	
	return (void*)0;
}

static int loops;

int main(int argc, char *agrv[])
{
	pthread_t tid1, tid2;
	int ret;
	
	/* 获取用户传递的参数 */
//	if(2 > argc) 
//	{
//		loops = 10000000;
//	}
//	else
//	{
//		loops = atoi(argv[1]);
//	}
	
	loops = 10000000;
	
	
	//初始化互斥锁
	pthread_mutex_init(&mutex, NULL); 
	
	/* 创建2个新线程 */
	/* 线程指针; 线程属性; 线程运行函数起始地址; 运行函数的参数*/
	ret = pthread_create(&tid1, NULL, new_thread_start, &loops);
	if(ret)
	{
		fprintf(stderr, "pthread_creadte error:%s\n", strerror(ret));
		exit(-1);
	} 
	
	ret = pthread_create(&tid2, NULL, new_thread_start, &loops);
	if(ret)
	{
		fprintf(stderr, "pthread_creadte error:%s\n", strerror(ret));
		exit(-1);
	} 
	
	/* 等待线程结束 */
	ret = pthread_join(tid1, NULL);
	if(ret)
	{
		fprintf(stderr, "pthread_join error:%s\n", strerror(ret));
		exit(-1);
	} 
	
	ret = pthread_join(tid2, NULL);
	if(ret)
	{
		fprintf(stderr, "pthread_join error:%s\n", strerror(ret));
		exit(-1);
	} 
	
	
	/*打印结果*/
	printf("g_count = %d\n", g_count);
	
	//进程执行完之前销毁互斥锁 
	pthrea_mutex_destroy(&mutex); 
	
	exit(0);
}

3、条件变量

条件变量用于自动阻塞线程,直到某个特定事件或者某个条件满足为止,通常情况下,条件变量和互斥锁一起搭配使用。使用条件变量包含两个动作:

1)一个线程等待某个条件满足而被阻塞;

2)另一个线程中,条件满足时发出“信号”。

生产者—消费者,使用条件变量和互斥锁实现线程同步

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

static pthread_mutex_t mutex;   //定义互斥锁
static pthread_cond_t cond;     //定义条件变量 
static int g_avail = 0;         //全局共享资源 


//消费者线程 
static void *consumer_thread(void *arg)
{
	for(int i=0; i<10; i++)
	{
		pthread_mutex_lock(&mutex);    //互斥锁上锁 
		
		while(0 > g_avail)
		{
			pthread_cond_wait(&cond, &mutex); //等待条件满足 
		}
		
		while(0 < g_avail)     //消费 
		{
			g_avail--;
			printf("consumer_thread: g_avail = %d\n", g_avail);
		}
		
		pthread_mutex_unlock(&mutex);   //互斥锁解锁 
	}
	
	return (void*)0;
}


//主线程(生产者) 
int main(int argc, char *agrv[])
{
	pthread_t tid;
	int ret;
	
	//初始化互斥锁和条件变量 
	pthread_mutex_init(&mutex, NULL); 
	pthread_cond_init(&cond, NULL); 
	
	/* 创建线程 */
	/* 线程指针; 线程属性; 线程运行函数起始地址; 运行函数的参数*/
	ret = pthread_create(&tid, NULL, consumer_thread, NULL);
	if(ret)
	{
		fprintf(stderr, "pthread_creadte error:%s\n", strerror(ret));
		exit(-1);
	} 
	
	for(int i=0; i<10; i++)
	{
		pthread_mutex_lock(&mutex);     //上锁
		g_avail++;                      //生产
		printf("product_thread: g_avail = %d\n", g_avail);
		pthread_mutex_unlock(&mutex);   //解锁
		pthread_cond_signal(&cond);     //向条件变量发信号 
	}
	
	exit(0);
}

4、自旋锁

自旋锁和互斥锁很相似,在访问共享资源之前对自旋锁进行上锁,在访问完成后释放自旋锁(解锁)。自旋锁较于互斥锁来说更加底层。

如果在获取自旋锁时,自旋锁处于未锁定状态,那么将立即获得锁(对自旋锁进行上锁);如果在获取自旋锁时,自旋锁已经处于锁定状态,那么获取所操作将会在原地“自旋”,知道该自旋锁的持有者释放了锁。

不同点:

1)互斥锁在无法获得锁时会让线程陷入阻塞等待状态,而自旋锁在无法获取到锁时,将会在原地自旋等待;

2)对同一自旋锁两次加锁必然导致死锁,而对同一互斥锁两次加锁不一定导致死锁(互斥锁有所种类型)。

自旋——调用者一直在循环查看该自旋锁的持有者是否已经释放了锁。

不足:一直占用CPU,致使CPU效率降低。

应用:内核代码中使用较多,在中断服务函数中可以使用。

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

static pthread_spinlock_t spin;   //定义自旋锁 
static int g_count = 0;

static void *new_thread_start(void *arg)
{
	int loops = *((int*)arg);
	int l_count, j;
	
	for(j = 0; j < loops; j++)
	{
		pthread_spin_lock(&spin);    //自旋锁上锁 
		
		l_count = g_count;
		l_count ++;
		g_count = l_count;
		
		pthread_spin_unlock(&spin);   //自旋锁解锁 
	}
	
	return (void*)0;
}

static int loops;
int main(int argc, char *agrv[])
{
	pthread_t tid1, tid2;
	int ret;
	
	/* 获取用户传递的参数 */
//	if(2 > argc) 
//	{
//		loops = 10000000;
//	}
//	else
//	{
//		loops = atoi(argv[1]);
//	}
	
	loops = 10000000;
	
	
	//初始化自旋锁 
	pthread_spin_init(&spin, PTHREAD_PROCESS_PRIVATE); 
	
	/* 创建2个新线程 */
	/* 线程指针; 线程属性; 线程运行函数起始地址; 运行函数的参数*/
	ret = pthread_create(&tid1, NULL, new_thread_start, &loops);
	if(ret)
	{
		fprintf(stderr, "pthread_creadte error:%s\n", strerror(ret));
		exit(-1);
	} 
	
	ret = pthread_create(&tid2, NULL, new_thread_start, &loops);
	if(ret)
	{
		fprintf(stderr, "pthread_creadte error:%s\n", strerror(ret));
		exit(-1);
	} 
	
	/* 等待线程结束 */
	ret = pthread_join(tid1, NULL);
	if(ret)
	{
		fprintf(stderr, "pthread_join error:%s\n", strerror(ret));
		exit(-1);
	} 
	
	ret = pthread_join(tid2, NULL);
	if(ret)
	{
		fprintf(stderr, "pthread_join error:%s\n", strerror(ret));
		exit(-1);
	} 
	
	
	/*打印结果*/
	printf("g_count = %d\n", g_count);
	
	//进程执行完之前销毁自旋锁 
	pthread_spin_destroy(&spin); 
	
	exit(0);
}

5、读写锁

读写锁(共享互斥锁)有多种状态:读模式下的加锁状态(以下简称读加锁状态)、写模式下的加锁状态(以下简称写加锁状态)和 不加锁状态,一次只有一个线程可以占有写模式的读写锁,但是可以有多个线程同时占有读模式的读写锁。——更高的并行性

规则:

1)当读写锁处于写加锁状态时,在这个锁被解锁之前,所有试图对这个锁进行加锁操作(不管是以读 模式加锁还是以写模式加锁)的线程都会被阻塞。

2) 当读写锁处于读加锁状态时,所有试图以读模式对它进行加锁的线程都可以加锁成功;但是任何以 写模式对它进行加锁的线程都会被阻塞,直到所有持有读模式锁的线程释放它们的锁为止。

读写锁非常适合于对共享数据读的次数远大于写的次数的情况文章来源地址https://www.toymoban.com/news/detail-687545.html

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

static pthread_rwlock_t rwlock;   //定义读写锁 
static int g_count = 0;

static void *read_thread(void *arg)
{
	int number = *((int*)arg);
	int j;
	
	for(j = 0; j < 10; j++)
	{
		pthread_rwlock_rdlock(&rwlock);    //以读模式获取锁 
		
		printf("读线程<%d>,g_count=%d\n", number+1, g_count);
		
		pthread_rwlock_unlock(&rwlock);   //解锁 
		sleep(1);
	}
	
	return (void*)0;
}


static void *write_thread(void *arg)
{
	int number = *((int*)arg);
	int j;
	
	for(j = 0; j < 10; j++)
	{
		pthread_rwlock_wrlock(&rwlock);    //以写模式获取锁 
		
		printf("写线程<%d>,g_count=%d\n", number+1, g_count+=20);
		
		pthread_rwlock_unlock(&rwlock);   //解锁 
		sleep(1);
	}
	
	return (void*)0;
}

static int nums[5] = {0, 1, 2, 3, 4};
int main(int argc, char *agrv[])
{
	pthread_t tid[10];
	int j;
	
	
	//初始化读写 
	pthread_rwlock_init(&rwlock, NULL); 
	/* 线程指针; 线程属性; 线程运行函数起始地址; 运行函数的参数*/
	/* 创建5个读g_count变量的线程 */
	for(j = 0; j<5;j++)
	{
		pthread_create(&tid[j], NULL, read_thread, &nums[j]);
	} 
	
	/* 创建5个写g_count变量的线程 */
	for(j = 0; j<5;j++)
	{
		pthread_create(&tid[j+5], NULL, write_thread, &nums[j]);
	} 
	
	/* 等待线程结束 */
	for(j = 0; j < 10; j++)
	{
		pthread_join(tid[j], NULL);  //回收线程 
	}
	
	
	//进程执行完之前销毁读写锁 
	pthread_rwlock_destroy(&rwlock); 
	
	exit(0);
}

到了这里,关于进程线程知识之线程同步的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包赞助服务器费用

相关文章

  • 【神行百里】python开启多线程(threading)与多进程(multiprocessing)运行

      由于处理数据过多,程序运行很慢,就学习了一下python开启多线程与多进程的方法,虽然最后也没用上,但还是记录总结一下,以备不时之需。   传送门:进程与线程认识,进程与线程通俗理解   简言之, 进程为资源分配的最小单元,线程为程序执行的最小单元

    2024年02月02日
    浏览(16)
  • 【Java基础教程】(四十二)多线程篇 · 上:多进程与多线程、并发与并行的关系,多线程的实现方式、线程流转状态、常用操作方法解析~

    【Java基础教程】(四十二)多线程篇 · 上:多进程与多线程、并发与并行的关系,多线程的实现方式、线程流转状态、常用操作方法解析~

    理解进程与线程的区别; 掌握Java 中多线程的两种实现方式及区别; 掌握线程的基本操作方法; 进程是程序的一次动态执行过程,它经历了从代码加载、执行到执行完毕的一个完整过程,这个过程也是进程本身从产生、发展到最终消亡的过程 。多进程操作系统能同时运行多

    2024年02月16日
    浏览(14)
  • Linux和windows进程同步与线程同步那些事儿(五):Linux下进程同步

    Linux和windows进程同步与线程同步那些事儿(一) Linux和windows进程同步与线程同步那些事儿(二): windows线程同步详解示例 Linux和windows进程同步与线程同步那些事儿(三): Linux线程同步详解示例 Linux和windows进程同步与线程同步那些事儿(四):windows 下进程同步 Linux和wi

    2024年02月02日
    浏览(9)
  • Linux和windows进程同步与线程同步那些事儿(三): Linux线程同步详解示例

    Linux和windows进程同步与线程同步那些事儿(一) Linux和windows进程同步与线程同步那些事儿(二): windows线程同步详解示例 Linux和windows进程同步与线程同步那些事儿(三): Linux线程同步详解示例 Linux和windows进程同步与线程同步那些事儿(四):windows 下进程同步 Linux和wi

    2024年02月01日
    浏览(11)
  • 操作系统进程线程(三)—进程状态、同步互斥、锁、死锁

    操作系统进程线程(三)—进程状态、同步互斥、锁、死锁

    原子操作的概念 原子操作就是不可中断的一个或者一系列操作。 原子操作如何实现 总线锁定 使用处理器提供的一个LOCK#信号,当一个处理器在总线上输出此信号的时候,其他处理器的请求将被阻塞住,那么该处理器可以独占内存。 缓存锁 总线锁开销比较大,因为把CPU和内

    2024年02月04日
    浏览(42)
  • 操作系统-进程和线程-同步、互斥、死锁

    操作系统-进程和线程-同步、互斥、死锁

    目录 一、同步互斥  二、互斥的实现方法 2.1软件实现 2.1.1单标志法 2.1.2双标志先检查 2.1.3双标志后检查 2.1.4Petersons算法 2.2硬件实现 2.2.1 TestAndSet指令 2.2.2 Swap指令   三、信号量机制 3.1整形变量  3.2 记录型变量  3.3用信号量实现进程互斥、同步、前驱关系 3.3.1互斥  3.3.2同步

    2024年02月08日
    浏览(44)
  • 【Java之家-编程的衣柜】线程的基础知识及线程与进程的联系

    【Java之家-编程的衣柜】线程的基础知识及线程与进程的联系

    线程是什么 一个线程就是一个 “执行流”. 每个线程之间都可以按照顺讯执行自己的代码. 多个线程之间 “同时” 执行 着多份代码. 轻量级进程 - 线程(Thread) 为什么要有线程 首先,“并发编程”成为“刚需” 其次, 虽然多进程也能实现 并发编程, 但是线程比进程更轻量

    2024年02月07日
    浏览(28)
  • 【已解决】C语言实现多线程的同步与异步

    【已解决】C语言实现多线程的同步与异步

    说真的写了这篇博文时,才知道c语言本身不支持多线程,而是一些windowsapi让c语言拥有多线程的能力,那下面内容就以打开对话框为例,展现如何实现多线程的同步与异步。 想要实现c语言打开多个对话框的多线程同步与异步 代码效果 对代码的查阅会发现,关键在于定义多线

    2024年02月02日
    浏览(8)
  • Redis的单线程与多线程

    Redis的单线程与多线程

    Redis的核心处理逻辑一直都是单线程 有一些分支模块是多线程 (某些异步流程从4.0开始用的多线程,例如UNLINK、FLUSHALL ASYNC、FLUSHDB ASYNC等非阻塞的删除操作。网络I/O解包从6.0开始用的是多线程;) 多线程多好啊可以利用多核优势 官方给的解释 意思就是Redis的定位,是内存k-v存储

    2024年02月13日
    浏览(8)
  • 不同开发语言在进程、线程和协程的设计差异

    不同开发语言在进程、线程和协程的设计差异

    在多线程项目开发时,最常用、最常遇到的问题是 1,线程、协程安全 2,线程、协程间的通信和控制 本文主要探讨不同开发语言go、java、python在进程、线程和协程上的设计和开发方式的异同。 进程 进程是 操作系统进行资源分配的基本单位,每个进程都有自己的独立内存空

    2024年01月22日
    浏览(13)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包