(七)Linux系统编程之线程(中)

一、原子操作

(七)Linux系统编程之线程(中)_第1张图片


二、死锁

造成死锁的原因:

  1. 自己锁自己
for(int i=0;i<MAX;++i){
	//加锁
	pthread_mutex_lock(&mutex);
	pthread_mutex_lock(&mutex);//自己锁自己
	int cur=number;
	cur++;
	number=cur;
	printf("Thread A,id = %lu, number = %d\n",pthread_self(),number);
	//解锁
	pthread_mutex_unlock(&mutex);
	usleep(10);
}

操作完成之后,一定要解锁。

(七)Linux系统编程之线程(中)_第2张图片


三、读写锁

1. 读写锁是几把锁?
(1)一把锁
(2)pthread_rwlock_t lock;


2. 读写锁的类型:
(1)读锁 - 对内存做读操作
(2)写锁 - 对内存做写操作


3. 读写锁的特性:
(1)线程A加读锁成功,又来了三个线程,做读操作,可以加锁成功【读共享 - 并行处理】
(2)线程A加写锁成功,又来了三个线程,做读操作,三个线程阻塞 【写独占
(3)线程A加读锁成功,又来了B线程加写锁阻塞,又来了C线程加读锁阻塞【读写不能同时;写的优先级高(写锁都阻塞了那么之后的读锁肯定阻塞)】


4. 读写锁场景练习

  • 线程A加写锁成功,线程B请求读锁
    线程B阻塞
  • 线程A持有读锁,线程B请求写锁
    线程B阻塞
  • 线程A持有读锁,线程B请求读锁
    线程B加锁成功
  • 线程A持有读锁,然后线程B请求写锁,然后线程C请求读锁
    B阻塞,C阻塞 - 写的优先级高
    A解锁,B线程加写锁成功
    B解锁,C加读锁成功
  • 线程A持有写锁,然后线程B请求读锁,然后线程C请求写锁
    BC阻塞
    A解锁,C加写锁成功,B继续阻塞
    C解锁,B加读锁

5、读写锁的使用场景

  • 互斥锁 - 读写串行

  • 读写锁:
    读:并行
    写:串行

  • 程序中的读操作>>写操作的时候


6、主要操作函数

  • 初始化读写锁
 pthread_rwlock_init(
 	pthread_rwlock_t *restrict rwlock,
 	const pthread_rwlockattr_t *restrict attr
 );
 
  • 销毁读写锁
pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
  • 加读锁
pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);

阻塞:之前对这把锁加的写锁的操作

  • 尝试加读锁
 pthread_rwlock_trydlock(pthread_rwlock_t *rwlock);

加锁成功:0
失败:错误号

  • 加写锁
pthread_rwlock_wrlock(pthread_rwlock_t *rwlocl);

上一次加锁写锁,还没有解锁的时候
上一次加读锁,没解锁

  • 尝试加写锁
pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
  • 解锁
pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

7、练习:
3个线程不定时写同一个全局资源,5个线程不定时读统一全局资源:

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

//全局变量
int number=0;

//创建读写锁
pthread_rwlock_t lock;

void* write_func(void* arg){
        //循环写
        while(1)
        {
                //加写锁
                pthread_rwlock_wrlock(&lock);
                number++;
                printf("== write:%lu,%d\n",pthread_self(),number);
                //解锁
                pthread_rwlock_unlock(&lock);
                usleep(500);
        }
        return NULL;
}

void* read_func(void* arg){
        while(1)
        {
                //加读锁
                pthread_rwlock_rdlock(&lock);
                printf("== read:%lu,%d\n",pthread_self(),number);
                pthread_rwlock_unlock(&lock);
                usleep(500);
        }
        return NULL;
}
int main(int argc,const char* argv[])
{
        pthread_t p[8];

        //init 读写锁
        pthread_rwlock_init(&lock,NULL);
        //创建3个写线程
        for(int i=0;i<3;++i){
                pthread_create(&p[i],NULL,write_func,NULL);
        }
        //创建5个读线程
        for(int i=3;i<8;++i){
                pthread_create(&p[i],NULL,read_func,NULL);
        }
        //阻塞回收子线程的pcb
        for(int i=0;i<8;++i){
                pthread_join(p[i],NULL);
        }

        //释放读写锁资源
        pthread_rwlock_destroy(&lock);

        return 0;
}

  • 先不加锁

8、读写锁、互斥锁

  • 阻塞线程
  • 不是什么时候都能阻塞线程
    链表 Node *head = NULL
    while(head)
    {
    //我们想让代码在这个位置阻塞
    //等待链表中有了节点之后再继续向下运行
    //使用了后面要讲的条件变量
    }
    //链表不为空的处理代码


四、条件变量

1、条件变量是锁吗?

  • 不是锁,但是条件变量能够阻塞线程

  • 使用条件变量+互斥量
    互斥量:保护一块共享资源
    条件变量:引起阻塞
    生产者和消费者模型
    (七)Linux系统编程之线程(中)_第3张图片
    2、条件变量的两个动作?

  • 条件不满足,阻塞线程

  • 当条件满足,通知阻塞的线程开始工作

3、条件变量的类型:pthread_cond_t;

4、主要函数:

  • 初始化一个条件变量 - condtion
pthread_cond_init(
	pthread_cond_t *restrict cond,
	const pthread_condattr_t *restrict attr
);
  • 销毁一个条件变量
pthread_cond_destroy(pthread_cond_t *cond);
  • 阻塞等待一个条件变量
pthread_cond_wait(
	pthread_cond_t *restrict cond,
	pthread_mutex_t *restrict mutex
);

(1)阻塞线程
(2)将已经上锁的mutex解锁
(3)该函数解除阻塞,会对互斥锁加锁

  • 限时等待一个条件变量
pthread_cond_timewait(
	pthread_cond_t *restrict cond,
	pthread_mutex_t *restrict mutex,
	const struct timespec *restrict abstime
);
  • 唤醒至少一个阻塞在条件变量上的线程
pthread_cond_signal(pthread_cond_t *restrict cond);
  • 唤醒全部阻塞在条件变量上的线程
pthread_cond_broadcast(pthread_cond_t *cond);

5、生产者和消费者:

(七)Linux系统编程之线程(中)_第4张图片

//节点结构
typedef struct node
{
        int data;
        struct node* next;
}Node;

//永远指向链表头部指针
Node* head=NULL;

//线程同步 - 互斥锁
pthread_mutex_t mutex;

//阻塞线程 - 条件变量类型变量
pthread_cond_t cond;

//生产者
void* producer(void* arg)
{
        while(1){
                //创建一个链表的节点
                Node* pnew=(Node*)malloc(sizeof(Node));
                //节点初始化
                pnew->data=rand()%1000;//0-999
                //使用互斥锁保护共享数据
                pthread_mutex_lock(&mutex);
                //指针域
                pnew->next=head;
                head=pnew;
                printf("====== produce:%lu,%d\n",pthread_self(),pnew->data);
                pthread_mutex_unlock(&mutex);

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

                sleep(rand()%3);
        }
        return NULL;
}
//消费者
void* customer(void* arg)
{
        while(1){
                //使用互斥锁保护共享数据
                pthread_mutex_lock(&mutex);
                //判断链表是否为空
                if(head==NULL){
                        //线程阻塞
                        //该函数会对互斥锁解锁
                        pthread_cond_wait(&cond,&mutex);
                        //解除阻塞之后,对互斥锁做加锁操作
                }
                //链表不为空,删除一个节点,删除头结点
                Node* pdel=head;
                head=head->next;
                printf("------ customer:%lu,%d\n",pthread_self(),pdel->data);
                free(pdel);
                pthread_mutex_unlock(&mutex);
        }
        return NULL;
}

int main(int argc,const char* argv[])
{
        pthread_t p1,p2;

        //init
        pthread_mutex_init(&mutex,NULL);
        pthread_cond_init(&cond,NULL);

        //创建生产者线程
        pthread_create(&p1,NULL,producer,NULL);
        //创建消费者线程
        pthread_create(&p2,NULL,customer,NULL);

        //阻塞回收子线程
        pthread_join(p1,NULL);
        pthread_join(p2,NULL);

        pthread_mutex_destroy(&mutex);
        pthread_cond_destroy(&cond);

        return 0;
}

你可能感兴趣的:(Linux系统编程)