Linux系统编程(线程同步)

文章目录

    • 线程同步的例子
    • 线程同步
    • 互斥锁
    • 死锁: 死锁不是linux提供给开发者的一种机制, 而是由于开发者操作不当引起的.
    • 读写锁:读写锁是一把锁
    • 读写锁使用步骤
    • 条件变量
    • 多个生成者和多个消费者程序在执行的时候core掉的原因分析
    • 信号量

线程同步的例子

创建两个线程,让两个线程共享一个全局变量int number,然后让每个线程数5000次数,看最后打印出这个number值是多少?

#include 
#include 
#include 
#include 
#include 

#define NUM 5000
int number = 0;

void* mythread1(void *arg)
{
    int i;
    int n;
    for(i = 0; i < NUM; ++i)
    {
        n = number;
        n++;
        number = n;
        printf("1:[%d]\n", number);
    }
}

void* mythread2(void* arg)
{
#include 
#include 
#include 
#include 
#include 

#define NUM 5000
int number = 0;

void* mythread1(void *arg)
{   
    int i;
    int n;
    for(i = 0; i < NUM; ++i)
    {   
        n = number;
        n++;
        number = n;
        printf("1:[%d]\n", number);
    }
}

void* mythread2(void* arg)
{   
    int i;
    int n;
    for(i = 0; i < NUM; ++i)
    {   
        n = number;
        n++;
        number = n;
        printf("2:[%d]\n", number);
    }
}

int main()
{
    pthread_t thread1;
    pthread_t thread2;

    int ret1 = pthread_create(&thread1, NULL, mythread1, NULL);
    if(ret1 != 0)
    {
        printf("pthread1 create error:[%s]\n", strerror(ret1));
    }

    int ret2 = pthread_create(&thread2, NULL, mythread2, NULL);
    if(ret2 != 0)
    {
        printf("pthread2 create error:[%s]\n", strerror(ret1));
    }

    pthread_join(thread1, NULL);
    pthread_join(thread2, NULL);


    return 0;
}

分析:

n = number;
n++;
number = n;
printf("1:[%d]\n", number);

Linux系统编程(线程同步)_第1张图片

线程同步

互斥锁: 线程A和线程B共同访问共享资源, 当线程A想访问共享资源的时候,要先获得锁, 如果锁被占用, 则加锁不成功需要阻塞等待对方释放锁; 若锁没有被占用, 则获得锁成功–加锁, 然后操作共享资源, 操作完之后, 必须解锁, 同理B也是和A一样.---->也就是说, 同时不能有两个线程访问共享资源, 属于互斥操作.

原子操作:该操作要么执行,要么就完成。

互斥锁

  1. 创建一把互斥锁
    pthread_mutex_t mutex;
  2. 初始化互斥锁
    pthread_mutex_init(&mutex);—相当于mutex=1
  3. 在代码中寻找共享资源(也称为临界区)
    pthread_mutex_lock(&mutex); – mutex = 0
    [临界区代码]
    pthread_mutex_unlock(&mutex); ++ mutex = 1
  4. 释放互斥锁资源
    pthread_mutex_destroy(&mutex);
    注意:必须在所有操作共享资源的线程上都加上锁否则不能起到同步的效果。

pthread_lock.c

#include 
#include 
#include 
#include 
#include 
#include 
#include 

//定义一把锁
pthread_mutex_t mutex;

void *mythread1(void *args)
{
	while(1)
	{
		//加锁
		pthread_mutex_lock(&mutex);

		printf("hello ");
		sleep(rand()%3);
		printf("world\n");
	
		//解锁
		pthread_mutex_unlock(&mutex);
		sleep(rand()%3);
	}

	pthread_exit(NULL);
}


void *mythread2(void *args)
{
	while(1)
	{
		//加锁
		pthread_mutex_lock(&mutex);

		printf("HELLO ");
		sleep(rand()%3);
		printf("WORLD\n");

		//解锁
		pthread_mutex_unlock(&mutex);
		sleep(rand()%3);
	}

	pthread_exit(NULL);
}

int main()
{
	int ret;
	pthread_t thread1;
	pthread_t thread2;

	//随机数种子
	srand(time(NULL));
	
	//互斥锁初始化
	pthread_mutex_init(&mutex, NULL);

	ret = pthread_create(&thread1, NULL, mythread1, NULL);
	if(ret!=0)
	{
		printf("pthread_create error, [%s]\n", strerror(ret));
		return -1;
	}

	ret = pthread_create(&thread2, NULL, mythread2, NULL);
	if(ret!=0)
	{
		printf("pthread_create error, [%s]\n", strerror(ret));
		return -1;
	}

	//等待线程结束
	pthread_join(thread1, NULL);
	pthread_join(thread2, NULL);

	//释放互斥锁
	pthread_mutex_destroy(&mutex);
	return 0;
}

死锁: 死锁不是linux提供给开发者的一种机制, 而是由于开发者操作不当引起的.

  1. 自己锁自己.
    注意点: 线程在异常退出的时候也需要解锁.

  2. A线程占用着A锁, 又想去获得B锁; B线程占用着B锁, 又想去获得A锁,
    两个线程都不释放自己的锁, 又想去获得对方的锁, 从而造成了死锁.

    解决方法:
    1 需要先释放自己的锁再去获得其他锁
    2 避免使用嵌套的锁, 让线程按照一定的顺序加锁
    3 可以调用pthread_mutex_trylock函数加锁, 该函数不阻塞, 所以不会产生死锁.

读写锁:读写锁是一把锁

读写锁场景:
1.线程A加写锁成功, 线程B请求读锁
  线程B阻塞,
  当线程A解锁之后, 线程B加锁成功

2.线程A持有读锁, 线程B请求写锁
  线程B会阻塞;
  当线程A解锁之后, 线程B加锁成功

3.线程A拥有读锁, 线程B请求读锁
  线程B请求锁成功

4.线程A持有读锁, 然后线程B请求写锁, 然后线程C请求读锁
  线程B和C都阻塞;
  当A释放锁之后, B先获得锁, C阻塞
  当B释放锁之后, C获得锁

5.线程A持有写锁, 然后线程B请求读锁, 然后线程C请求写锁
  线程B和C都阻塞;
  当线程A解锁之后, C先获得锁, B阻塞;
  当C解锁之后, B获得锁

读写锁总结
  写独占, 读共享, 当读和写一起等待锁的时候, 写的优先级高

读写锁使用步骤

  1. 先定义一把读写锁:
    pthread_rwlock_t rwlock;
  2. 初始化读写锁
    pthread_rwlock_init(&rwlock, NULL);
  3. 加锁
    pthread_rwlock_rdlock(&rwlock);---->加读锁
    pthread_rwlock_wrlock(&rwlock);---->加写锁

    共享资源出现的位置
    /
  4. 解锁
    pthread_rwlock_unlock(&rwlock);
  5. 释放锁
    pthread_rwlock_destroy(&rwlock);

pthread_rwlock.c

//读写锁测试程序
#include 
#include 
#include 
#include 
#include 
#include 

int number = 0;

//定义一把读写锁
pthread_rwlock_t rwlock;

//写线程回调函数
void *thread_write(void *arg)
{
	int i = *(int *)arg;

	int cur;

	while(1)
	{
		//加写锁
		pthread_rwlock_wrlock(&rwlock);

		cur = number;
		cur++;
		number = cur;	
		printf("[%d]-W:[%d]\n", i, cur);

		//解锁
		pthread_rwlock_unlock(&rwlock);
		sleep(rand()%3);
	}
}

//读线程回调函数
void *thread_read(void *arg)
{
	int i = *(int *)arg;
	int cur;

	while(1)
	{
		//加读锁
		pthread_rwlock_rdlock(&rwlock);

		cur = number;
		printf("[%d]-R:[%d]\n", i, cur);

		//解锁
		pthread_rwlock_unlock(&rwlock);
		sleep(rand()%3);
	}	
}

int main()
{
	int n = 8;
	int i = 0;
	int arr[8];
	pthread_t thread[8];

	//读写锁初始化
	pthread_rwlock_init(&rwlock, NULL);

	//创建3个写子线程
	for(i=0; i<3; i++)
	{
		arr[i] = i;
		pthread_create(&thread[i], NULL, thread_write, &arr[i]);
	}

	//创建5个读子线程
	for(i=3; i<n; i++)
	{
		arr[i] = i;
		pthread_create(&thread[i], NULL, thread_read, &arr[i]);
	}

	//回收子线程
	int j = 0;
	for(j=0;j<n; j++)
	{
		pthread_join(thread[j], NULL);
	}

	//释放锁
	pthread_rwlock_destroy(&rwlock);

	return 0;
}

条件变量

  1. 定义条件变量
    pthread_cont_t cond;
  2. 初始化条件变量
    pthread_cond_init(&cond, NULL);
  3. 在生成者线程中调用:
    pthread_cond_signal(&cond);
  4. 在消费者线程中调用:
    pthread_cond_wait(&cond, &mutex);
  5. 释放条件变量
    pthread_cond_destroy(&cond);

Linux系统编程(线程同步)_第2张图片pthread_cond.c

//使用条件变量实现生产者和消费者模型
#include 
#include 
#include 
#include 
#include 
#include 
typedef struct node
{
	int data;
	struct node *next;
}NODE;

NODE *head = NULL;

//定义一把锁
pthread_mutex_t mutex;

//定义条件变量
pthread_cond_t cond;

//生产者线程
void *producer(void *arg)
{
	NODE *pNode = NULL;
	while(1)
	{
		//生产一个节点
		pNode = (NODE *)malloc(sizeof(NODE));
		if(pNode==NULL)
		{
			perror("malloc error");
			exit(-1);
		}
		pNode->data = rand()%1000;
		printf("P:[%d]\n", pNode->data);

		//加锁
		pthread_mutex_lock(&mutex);

		pNode->next = head;
		head = pNode;

		//解锁
		pthread_mutex_unlock(&mutex);

		//通知消费者线程解除阻塞
		pthread_cond_signal(&cond);
		
		sleep(rand()%3);
	}
}


//消费者线程
void *consumer(void *arg)
{
	NODE *pNode = NULL;
	while(1)
	{
		//加锁
        pthread_mutex_lock(&mutex);
		
		if(head==NULL)
		{
			//若条件不满足,需要阻塞等待
			//若条件不满足,则阻塞等待并解锁;
			//若条件满足(被生成者线程调用pthread_cond_signal函数通知),解除阻塞并加锁 
			pthread_cond_wait(&cond, &mutex);
		}

		printf("C:[%d]\n", head->data);	
		pNode = head;
		head = head->next;

		//解锁
		pthread_mutex_unlock(&mutex);

		free(pNode);
		pNode = NULL;

		sleep(rand()%3);
	}
}

int main()
{
	int ret;
	pthread_t thread1;
	pthread_t thread2;

	//初始化互斥锁
	pthread_mutex_init(&mutex, NULL);

	//条件变量初始化
	pthread_cond_init(&cond, NULL);

	//创建生产者线程
	ret = pthread_create(&thread1, NULL, producer, NULL);
	if(ret!=0)
	{
		printf("pthread_create error, [%s]\n", strerror(ret));
		return -1;
	}

	//创建消费者线程
	ret = pthread_create(&thread2, NULL, consumer, NULL);
	if(ret!=0)
	{
		printf("pthread_create error, [%s]\n", strerror(ret));
		return -1;
	}

	//等待线程结束
	pthread_join(thread1, NULL);
	pthread_join(thread2, NULL);

	//释放互斥锁
	pthread_mutex_destroy(&mutex);

	//释放条件变量
	pthread_cond_destroy(&cond);

	return 0;
}

多个生成者和多个消费者程序在执行的时候core掉的原因分析

假若只有一个生产者生产了一个节点, 此时会调用pthread_cond_signal通知消费者线程, 此时若有多个消费者被唤醒了, 则最终只有消费者获得锁, 然后进行消费, 此时会将head置为NULL, 然后其余的几个消费者线程只会有一个线程获得锁, 然后读取head的内容就会core掉.

pthread_cond_mul.c

//使用条件变量实现生产者和消费者模型
#include 
#include 
#include 
#include 
#include 
#include 
typedef struct node
{
	int data;
	struct node *next;
}NODE;

NODE *head = NULL;

//定义一把锁
pthread_mutex_t mutex;

//定义条件变量
pthread_cond_t cond;

//生产者线程
void *producer(void *arg)
{
	NODE *pNode = NULL;
	int n = *(int *)arg;
	while(1)
	{
		//生产一个节点
		pNode = (NODE *)malloc(sizeof(NODE));
		if(pNode==NULL)
		{
			perror("malloc error");
			exit(-1);
		}
		pNode->data = rand()%1000;
		printf("P[%d]:[%d]\n", n, pNode->data);

		//加锁
		pthread_mutex_lock(&mutex);

		pNode->next = head;
		head = pNode;

		//解锁
		pthread_mutex_unlock(&mutex);

		//通知消费者线程解除阻塞
		pthread_cond_signal(&cond);

		sleep(rand()%3);
	}
}


//消费者线程
void *consumer(void *arg)
{
	NODE *pNode = NULL;
	int n = *(int *)arg;
	while(1)
	{
		//加锁
		pthread_mutex_lock(&mutex);

		if(head==NULL)
		{
			//若条件不满足,需要阻塞等待
			//若条件不满足,则阻塞等待并解锁;
			//若条件满足(被生成者线程调用pthread_cond_signal函数通知),解除阻塞并加锁 
			pthread_cond_wait(&cond, &mutex);
		}

		if(head==NULL)
		{
			//解锁
			pthread_mutex_unlock(&mutex);	
			continue;
		}

		printf("C[%d]:[%d]\n", n, head->data);	
		pNode = head;
		head = head->next;

		//解锁
		pthread_mutex_unlock(&mutex);

		free(pNode);
		pNode = NULL;

		sleep(rand()%3);
	}
}

int main()
{
	int ret;
	int i = 0;
	pthread_t thread1[5];
	pthread_t thread2[5];

	//初始化互斥锁
	pthread_mutex_init(&mutex, NULL);

	//条件变量初始化
	pthread_cond_init(&cond, NULL);

	int arr[5];
	for(i=0; i<5; i++)
	{
		arr[i]= i;
		//创建生产者线程
		ret = pthread_create(&thread1[i], NULL, producer, &arr[i]);
		if(ret!=0)
		{
			printf("pthread_create error, [%s]\n", strerror(ret));
			return -1;
		}

		//创建消费者线程
		ret = pthread_create(&thread2[i], NULL, consumer, &arr[i]);
		if(ret!=0)
		{
			printf("pthread_create error, [%s]\n", strerror(ret));
			return -1;
		}
	}

	//等待线程结束
	for(i=0; i<5; i++)
	{
		pthread_join(thread1[i], NULL);
		pthread_join(thread2[i], NULL);
	}

	//释放互斥锁
	pthread_mutex_destroy(&mutex);

	//释放条件变量
	pthread_cond_destroy(&cond);

	return 0;
}

在使用条件变量的线程中, 能够引起线程的阻塞的地方有两个:

  1. 在条件变量处引起阻塞---->这个阻塞会被pthread_cond_signal解除阻塞
  2. 互斥锁也会使线程引起阻塞----->其他线程解锁会使该线程解除阻塞.

信号量

  1. 定义信号量变量
    sem_t sem1;
    sem_t sem2;

  2. 初始化信号量
    sem_init(&sem1, 0, 5);
    sem_init(&sem2, 0, 5);

  3. 加锁
    sem_wait(&sem1);
    //共享资源
    sem_post(&sem2);

    sem_wait(&sem2);
    //共享资源
    sem_post(&sem1);

  4. 释放资源
    sem_destroy(sem1);
    sem_destroy(sem2);

Linux系统编程(线程同步)_第3张图片

pthread_sem.c

//使用信号量实现生产者和消费者模型
#include 
#include 
#include 
#include 
#include 
#include 
#include 
typedef struct node
{
	int data;
	struct node *next;
}NODE;

NODE *head = NULL;

//定义信号量
sem_t sem_producer;
sem_t sem_consumer;

//生产者线程
void *producer(void *arg)
{
	NODE *pNode = NULL;
	while(1)
	{
		//生产一个节点
		pNode = (NODE *)malloc(sizeof(NODE));
		if(pNode==NULL)
		{
			perror("malloc error");
			exit(-1);
		}
		pNode->data = rand()%1000;
		printf("P:[%d]\n", pNode->data);

		//加锁
		sem_wait(&sem_producer); //--

		pNode->next = head;
		head = pNode;

		//解锁
		sem_post(&sem_consumer);  //相当于++

		sleep(rand()%3);
	}
}


//消费者线程
void *consumer(void *arg)
{
	NODE *pNode = NULL;
	while(1)
	{
		//加锁
		sem_wait(&sem_consumer); //相当于--
		
		printf("C:[%d]\n", head->data);	
		pNode = head;
		head = head->next;

		//解锁
		sem_post(&sem_producer); //相当于++

		free(pNode);
		pNode = NULL;

		sleep(rand()%3);
	}
}

int main()
{
	int ret;
	pthread_t thread1;
	pthread_t thread2;

	//初始化信号量
	sem_init(&sem_producer, 0, 5);
	sem_init(&sem_consumer, 0, 0);

	//创建生产者线程
	ret = pthread_create(&thread1, NULL, producer, NULL);
	if(ret!=0)
	{
		printf("pthread_create error, [%s]\n", strerror(ret));
		return -1;
	}

	//创建消费者线程
	ret = pthread_create(&thread2, NULL, consumer, NULL);
	if(ret!=0)
	{
		printf("pthread_create error, [%s]\n", strerror(ret));
		return -1;
	}

	//等待线程结束
	pthread_join(thread1, NULL);
	pthread_join(thread2, NULL);

	//释放信号量资源
	sem_destroy(&sem_producer);
	sem_destroy(&sem_consumer);

	return 0;
}

你可能感兴趣的:(Linux)