POSIX线程编程(下):线程的同步与异步

一、竞争与同步

当多个线程同时访问其共享资源时,由于操作时间不协调,导致数据不一致、不完成,这种现象叫同步
而我们需要在访问共享资源时达到异步的效果。

二、互斥量(锁)

pthread_mutex_t 

互斥锁,是解决同步问题的一项技术。

int pthread_mutex_init (pthread_mutex_t *__mutex,const pthread_mutexattr_t *__mutexattr)

功能:初始一个互斥锁,也可以使用宏PTHREAD_MUTEX_INITIALIZER初始化
mutexattr:锁的属性,可以为NULL,使用缺省的参数。
返回值:成功返回0,失败返回错误码。

int pthread_mutex_destroy (pthread_mutex_t *__mutex)

功能:销毁一个互斥锁

int pthread_mutex_lock (pthread_mutex_t *__mutex)

功能:执行加锁操作,如果该已经处于锁的状态,则该线程阻塞

int pthread_mutex_unlock (pthread_mutex_t *__mutex)

功能:执行解锁操作

int pthread_mutex_trylock (pthread_mutex_t *__mutex)

功能:添加测试锁,如果不加锁刚立即返回

int pthread_mutex_timedlock(pthread_mutex_t *restrict mutex,
          const struct timespec *restrict abs_timeout);

struct timespec
{
    time_t tv_sec;        /* Seconds.  */
    long int tv_nsec;     /* Nanoseconds.*/ 1秒= 1000000000 纳秒
};

功能:添加时锁,倒计时,如果超时还不加上则立即返回。

三、死锁

什么是死锁:多个线程互相等待对方资源,在得到所需要的资源之前都不会释放自己的资源,然后造成循环等待的现象,称为死锁。

死锁产生四大必要条件:只要有一个不满足就不能构成死锁。

	1、资源互斥
	2、占有且等待
    3、资源不可剥夺
    4、环路等待

如休防止出现死锁:四个条件只有一个被破坏,就不能构成死锁。、

1、破坏互斥条件,让资源能够共享使用(准备多份)
2、破坏占且等待的条件,一次申请完成它所有需要的资源,资源没有满足前不让它运行,一旦开始运行就一直归它所有。

缺点:系统资源会被浪费。

3、破坏不可剥夺的条件,当已经占有了一些资源,请求新的资源而获取不到,然后就释放已经获取到的资源。

缺点:实现起来比较复杂,释放已经获取到的资源可能会造成前一阶段的工作浪费。

4、破坏循环等待的条件,采用顺序分配资源的方法,在系统中为资源进行编号,规定线程必须按照编号递增的顺序获取资源。

	缺点:资源必须相对稳定,这样就限制了资源的增加和减少。

四、条件变量

条件变量可以让线程在满足特定的条件下暂停或继续,要和互斥量配合使用(线程自己进入休眠,然后被别人唤醒)。
pthread_cond_t 
int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr);
功能:初始化条件变量,也可以使用宏PTHREAD_COND_INITIALIZER初始化。

int pthread_cond_destroy(pthread_cond_t *cond);
功能:销毁条件变量

int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);
功能:让当前线程进入暂停状态(睡入条件变量),并随便让一个互斥锁解锁。

int pthread_cond_timedwait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex,const struct timespec *restrict abstime);
功能:让当前线程进入暂停状态(睡入条件变量),且设置它的睡眠时间

int pthread_cond_signal(pthread_cond_t *cond);
功能:从一个条件变量中唤醒一个线程
注意:线程醒的瞬间会再次把锁加上,如果不能加上会继续等待加锁。

int pthread_cond_broadcast(pthread_cond_t *cond);
功能:从条件变量中唤醒所有线程

五、生产者与消费模型

生产者:产生数据的线程
消费者:使用数据的线程
仓库:数据缓冲区

问题1:生产快,消费慢 则会爆仓
问题2:消费快,生产慢 则会饿死

解决方案:使用互斥锁与条件变量解决。
爆仓 让产生数据的线程睡入爆仓条件变量,同时唤醒饿死条件变量中的线程。
饿死 让使用数据的线程睡入饿死条件变量,同时唤醒爆仓条件变量中的线程。

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

#define MAX_SIZE 50		// 容量
char arr[MAX_SIZE];		// 仓库
size_t cnt = 0;			// 库存

pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;	// 访问仓库的锁
pthread_cond_t  full = PTHREAD_COND_INITIALIZER;	// 仓库满的条件变量
pthread_cond_t empty = PTHREAD_COND_INITIALIZER;    // 仓库空的条件变量

void show(const char* who,const char* op,char ch)
{
	for(int i=0; i<cnt; i++)
	{
		printf("%c",arr[i]);
	}
	printf("%s%c:%s\n",op,ch,who);
}

// 生产者线程
void* producer(void* arg)
{
	const char* who = arg;
	for(;;)
	{
		// 加锁访问仓库
		pthread_mutex_lock(&lock);
		
		// 判断仓库是否满
		while(cnt >= MAX_SIZE)
		{
			printf("%s:满仓!\n",who);
			// 睡入满仓条件变量
			pthread_cond_wait(&full,&lock);
		}

		char ch = 'A' + rand()%26;
		show(who,"<--",ch);
		arr[cnt++] = ch;

		// 叫一个消费者
		pthread_cond_signal(&empty);
		pthread_mutex_unlock(&lock);

		usleep(rand()%100*1000);
	}
}

// 消费者线程
void* customer(void* arg)
{
	const char* who = arg;
	for(;;)
	{
		// 加锁访问仓库
		pthread_mutex_lock(&lock);

		// 判断仓库是否为
		while(0 == cnt)
		{
			printf("%s:空仓!\n",who);
			// 睡入空仓条件变量
			pthread_cond_wait(&empty,&lock);
		}

		char ch = arr[cnt--];
		show(who,"->",ch);
		
		// 叫醒一个生产者
		pthread_cond_signal(&full);
		pthread_mutex_unlock(&lock);
	
		usleep(rand()%100*1000);
	}
}

int main()
{
	srand(time(NULL));
	
	pthread_t tid[6];
	for(int i=0; i<3; i++)
	{
		pthread_create(&tid[i],NULL,producer,"生产");
	}

	for(int i=3; i<6; i++)
	{
		pthread_create(&tid[i],NULL,customer,"消费");
	}

	for(int i=0; i<6; i++)
	{
		pthread_join(tid[i],NULL);
	}
}

六、信号量(跟进程的类似)

信号量是一个计数器,与进程的信号量原理相同,用于控制访问共享资源的线程数量。

#include <semaphore.h>

int sem_init(sem_t *sem, int pshared, unsigned int value);

功能:获取一个信号量并初始化
sem:是一个输出性质的参数,用于获取信号量的ID
pshared

  0 表示只在当时进程下使用
  非0 表示多个进程间共享使用(相当于进程间使用的信号量,但是Linux系统不支持)。

value:信号量初始值

int sem_wait(sem_t *sem);

功能:对信号量进行减一操作,不够减则阻塞

int sem_trywait(sem_t *sem);

功能:对信号量进行减一操作,不够减则返回非零值

int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);

功能:对信号量进行减一操作,不够减则倒计时,超时后返回非零值

int sem_post(sem_t *sem);

功能:对信号量进行加一操作

 int sem_destroy(sem_t *sem);

功能销毁信号量

int sem_getvalue(sem_t *sem, int *sval);

功能:获取信号量的值

猜你喜欢

转载自blog.csdn.net/perror_0/article/details/106749987
今日推荐