Linux多线程入门详解(二)互斥和同步

文章讲述了在多线程编程中,如何使用互斥锁(如pthread_mutex)解决资源竞争问题,以及信号量(semaphore)如何实现线程同步,确保按照特定顺序访问临界资源。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

如果创建多个线程,在访问主线程的某一个资源时候,会不会产生多个线程同时访问而产生的资源竞争问题,为了解决以上问题,我们研究了互斥与同步,分别对应互斥锁的设置以及信号量的设置,那么本文就将详细的为大家介绍互斥锁的问题。

1、互斥锁


互斥:当某进程进行到某一阶段执行某一段代码时候,其他线程不能运行他们中的某一片段(临界资源的排他性访问)

互斥存在的意义就是为了解决多个线程在操作临界资源(共享变量,全局变量,共享内存)时存在资源竞争问题

框架: 定义互斥锁 ==》初始化锁 ==》加锁 ==》解锁 ==》销毁

1.1、定义互斥锁

pthread_mutex_t

功能:创建一个互斥锁变量,为后续的锁内容信息存放创建一个变量接收


1.2、初始化锁

int pthread_mutex_init( pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);

功能:将已经定义好的互斥锁初始化。
参数:
mutex 要初始化的互斥锁
attr---初始化的值,一般是NULL表示默认锁           
返回值:
成功--0
失败--非零


1.3、加锁

int pthread_mutex_lock(pthread_mutex_t *mutex);

功能:用指定的互斥锁开始加锁代码,加锁后的代码到解锁部分的代码属于原子操作,在加锁期间其他进程/线程都不能操作该部分代码如果该函数在执行的时候,mutex已经被其他部分使用则代码阻塞。

参数: mutex 用来给代码加锁的互斥锁
返回值:成功 0 失败 非零


1.4、解锁

int pthread_mutex_unlock(pthread_mutex_t *mutex);

功能:将指定的互斥锁解锁。(解锁之后代码不再排他访问,一般加锁解锁同时出现)
参数:用来解锁的互斥锁
返回值:成功 0 失败 非零


1.5、销毁

int pthread_mutex_destroy(pthread_mutex_t *mutex);

功能:使用互斥锁完毕后需要销毁互斥锁
参数:mutex 要销毁的互斥锁
返回值:成功--0  失败--非零  
 

int cnt = 0;
//1.定义一个锁对象
pthread_mutex_t mutex;
void *th_fun1(void *arg)
{
	int i = 0;
	for (i = 0; i < 50000; i++)	{
		pthread_mutex_lock(&mutex);	//3.加锁
		cnt = cnt + 1;
		printf("cnt = %d\n", cnt);
		pthread_mutex_unlock(&mutex); //4.解锁
	}
}
 
void *th_fun2(void *arg)
{
	int i = 0;
	for (i = 0; i < 50000; i++)	{
		pthread_mutex_lock(&mutex); // 3.加锁
		cnt = cnt + 1;
		printf("cnt = %d\n", cnt);
		pthread_mutex_unlock(&mutex); //4.解锁
	}
}
 
int main(int argc, const char *argv[])
{
 
	pthread_t tid[2];
	
	//2.按照默认属性初始化锁对象
	pthread_mutex_init(&mutex, NULL);
 
	pthread_create(&tid[0], NULL, th_fun1, NULL);
	pthread_create(&tid[1], NULL, th_fun2, NULL);
 
	pthread_join(tid[0], NULL);
	pthread_join(tid[1], NULL);
	
	//5.销毁锁对象
	pthread_mutex_destroy(&mutex);
 
	return 0;
}
2、信号量

书接上文,我们讲了线程的高阶讲解,线程的控制,互斥与同步中的互斥,那么本文将为大家讲解线程的控制同步这一项,在讲解之前先为大家讲述一下,互斥与同步的区别

互斥:当某进程进行到某一阶段执行某一段代码时候,其他线程不能运行他们中的某一片段(临界资源的排他性访问)。
同步:要按照一定的先后顺序进行线程的顺序进行操作。
都是为了解决临界资源的竞争问题而设置的,那么具体的区别就是互斥没有顺序的控制而同步有了顺序的控制

 互斥在作用上包含同步

那么下文将为大家二进行同步的实现(信号量)的介绍

信号量的分类:

无名信号量:线程间通信
有名信号链:进程间通信
本文为线程的进阶介绍,所以将为大家进行无名信号量的实现

框架:信号量的定义-->信号量的初始化-->信号量的pv操作-->信号量销毁

2.1、信号量的定义

sem_t sem;
功能:定义一个信号量类型的信号量变量(普遍为全局变量)


2.2、信号量的初始化
#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);

功能:将已经定义好的信号量赋值。
 参数说明:

  • sem:要初始化的信号量
  • pshared:0 表示线程间使用信号量,非0 表示进程间使用信号量
  •  value 信号量的初始值,一般无名信号量,0 表示红灯,进程暂停阻塞,1 表示绿灯,进程可以通过执行

返回值:
成功  0
失败  -1


2.3、信号量的pv操作


p-->申请资源-->申请一个信号量
v-->释放资源-->释放一个信号量
申请信号量

int sem_wait(sem_t *sem);

功能:判断当前sem信号量是否有资源可用。(如果sem有资源(==1),则申请该资源,程序继续运行,如果sem没有资源(==0),则线程阻塞等待,一旦有资源则自动申请资源并继续运行程序,sem 申请资源后会自动执行 sem = sem - 1)
参数:sem 要判断的信号量资源
返回值:
成功 0 
失败 -1
释放信号量

int sem_post(sem_t *sem);

功能:函数可以将指定的sem信号量资源释放,并默认执行,sem = sem+1 ( 线程在该函数上不会阻塞)
参数:sem 要释放资源的信号量
返回值:
成功 0
失败 -1


2.4、信号量的销毁
int sem_destroy(sem_t *sem);

功能:使用完毕将指定的信号量销毁
参数:sem要销毁的信号量
返回值:
成功 0
失败  -1
 

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <semaphore.h>
#include <pthread.h>
 
// 链表的节点
struct Node
{
    int number;
    struct Node* next;
};
 
// 生产者线程信号量
sem_t psem;
// 消费者线程信号量
sem_t csem;
 
// 互斥锁变量
pthread_mutex_t pmutex;


// 指向头结点的指针
struct Node * head = NULL;
 
// 生产者的回调函数
void* producer(void* arg)
{
	int temp = *(int *)arg;
    // 一直生产
    while(1) {
        // 生产者拿一个信号灯
        sem_wait(&psem);
        // 创建一个链表的新节点
		pthread_mutex_lock(&pmutex);
        struct Node* pnew = (struct Node*)malloc(sizeof(struct Node));
        // 节点初始化
        pnew->number = rand() % 1000;
        // 节点的连接, 添加到链表的头部, 新节点就新的头结点
        pnew->next = head;
        // head指针前移
        head = pnew;
        printf("+++producer = %d, number = %d, tid = %ld\n", temp, pnew->number, pthread_self());
		pthread_mutex_unlock(&pmutex);
        // 通知消费者消费, 给消费者加信号灯
        sem_post(&csem);
        
 
        // 生产慢一点
		sleep(1);
        //sleep(rand() % 3);
    }
    return NULL;
}
 
// 消费者的回调函数
void* consumer(void* arg)
{
	int temp = *(int *)arg;
    while(1) {
        sem_wait(&csem);
        // 取出链表的头结点, 将其删除
		pthread_mutex_lock(&pmutex);
        struct Node* pnode = head;
		if (pnode == NULL) {
			printf("--consumer = %d can not consume: tid = %ld\n", temp, pthread_self());
		} else {
			printf("--consumer = %d: number: %d, tid = %ld\n", temp, pnode->number, pthread_self());
			head  = pnode->next;
			free(pnode);
		}
        
		pthread_mutex_unlock(&pmutex);
        // 通知生产者生成, 给生产者加信号灯
        sem_post(&psem);
		sleep(1);
        //sleep(rand() % 3);
    }
    return NULL;
}
 
int main()
{
    // 初始化信号量
    // 生产者和消费者拥有的信号灯的总和为1
    sem_init(&psem, 0, 5);  // 生成者线程一共有5个信号灯
    sem_init(&csem, 0, 0);  // 消费者线程一共有0个信号灯
	
	pthread_mutex_init(&pmutex, NULL);
    // 创建5个生产者, 5个消费者
    pthread_t ptid[5];
    pthread_t ctid[5];
    for(int i=0; i<5; ++i) {
        pthread_create(&ptid[i], NULL, producer, (void *)&i);
		pthread_create(&ctid[i], NULL, consumer, (void *)&i);
    }

	for(int i=0; i<5; ++i) {
        pthread_join(ptid[i], NULL);
		pthread_join(ctid[i], NULL);
    }
    sem_destroy(&psem);
    sem_destroy(&csem);
 
	pthread_mutex_destroy(&pmutex);
    return 0;
}

以上的内容就是线程的同步应用,通过信号量的处理,可以更好的解决面对临界资源竞争以及希望子线程的按照想要的顺序进行的问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

小朋友的大哈

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

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

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

打赏作者

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

抵扣说明:

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

余额充值