Linux 线程的同步与互斥

多个线程并发共同操作临界资源会出问题

各个线程代码的访问公共变量的代码段中,加减 不是原子操作, 判断前后等等任意地方,一个线程的代码会被CPU切出,另一个线程重新来执行代码。这会发生逻辑错误。为此我们必须提供同步与互斥机制。

互斥机制

1、同一时刻只能有一一个线程的代码在临界区内执行。
2、多个线程竞争进入没有代码证在执行的临界区时,只有一个线程可以进入。
3、没有在临界区内代码不可以阻碍别的线程代码进入临界区。·

要做到这些就必须有互斥量 (互斥锁)。

静态与动态申请锁 与释放锁
pthread_mutex_t    lock;
int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);

第一个参数 &lock 第二个参数NULL;

销毁互斥量:
已经加锁的互斥量不可以销毁, 已经销毁的互斥量不可以加锁

int pthread_mutex_destroy(pthread_mutex_t *mutex); 

加锁与解锁操作:

int   pthread_mutex_lock(pthread_mutex_t*    mutex);
int   pthread_mutex_unlock(pthread_mutex_t*    mutex);
加锁解锁的原理

计算机在硬件上提供了 加锁解锁的原子操作。比如体系结构中swap xchge 命令
保障原子的完成寄存器和内存功能的交换。

关于死锁

出现死锁的几个场景:
1、一个线程获得了这把锁之后(没有释放)又重新要获取这把锁。
2、线程A获取到了锁1 线程B获取到了锁2 这是A试图获取2 B试图获取1。类似问题当线程和锁多了以后就更加容易出现。
3、若干线程之间形成一种循环等待锁的关系。

四个必要条件:
1、 一个锁只能同时被一个线程拿到。
2、 一个线程申请别的锁阻塞时,不会自己已经得到的锁。
3、一个线程持有的锁不可以被其他线程剥夺。

尽量避免死锁要注意的:
加锁的临界区代码短平快:
加锁粒度小。
加锁解锁之间的代码简洁明了,逻辑简单,中途不再申请锁。、
加锁解锁之间的代码运行速度快。

关于无锁队列:

http://www.360doc.com/content/14/0811/10/1073512_400983810.shtml

线程安全与可重入

可重入函数:函数在多个执行流下同时被调用不会出现问题。
线程安全: 函数在多线程下同时被调用不会出现问题。

除多线程以外,信号处理函数也属于别的执行流。 可重入函数要求比线程安全·函数更加严格。

如果信号处理函数中加锁, 当多线程中有的线程拿到了这把锁但是还没有释放,这时进程收到了信号 进入信号处理函数中尝试获取锁,可是获取不到被阻塞,信号处理函数被阻塞则不回回到进程执行流中。造成死锁。

条件变量

线程之间不光需要互斥, 同样也需要同步。
如:一个优先级很高的线程争取到锁了以后发现临界资源中要读的数据还没有来,于是把锁释放 而其优先级高又重新获取到了锁。循环往复 ,线程空转还浪费锁资源。

条件变量保证同步机制:

运用了条件变量告诉 一个线程有效进入临界资源的条件满足了 ,线程再尝试获取临界资源的锁。

条件变量的初始化:
pthread_cond_t cond;

 int pthread_cond_init(pthread_cond_t *restrict cond,
              const pthread_condattr_t *restrict attr);

attr 设为NULL;

完事后要销毁条件变量。

 int pthread_cond_destroy(pthread_cond_t *cond);

发现条件满足时唤醒其他线程等待:
等待条件满足:

 int pthread_cond_broadcast(pthread_cond_t *cond);//唤醒所有的thread
 int pthread_cond_signal(pthread_cond_t *cond);//唤醒n个thread中的一个
具体那一个操作系统管

 int pthread_cond_wait(pthread_cond_t *restrict cond,
              pthread_mutex_t *restrict mutex);

抛开互斥只看同步·:

pthread_cond_t g_cond;
pthread_mutex_t g_lock;

void* ThreadEntry2(void* arg)
{
    (void*) arg;
    while(1){
        printf("22222222\n");
        pthread_cond_signal(&g_cond);
        usleep(500000);
    }
    return NULL;
}

void* ThreadEntry1(void* arg)
{
    (void*)arg;
    while(1){
        pthread_cond_wait(&g_cond, &g_lock);
        printf("11111\n");
        usleep(1000000);
    }
    return NULL;
}

int main()
{
    pthread_cond_init(&g_cond, NULL);
    pthread_mutex_init(&g_lock, NULL);

    pthread_t tid1, tid2;
    pthread_create(&tid1, NULL, ThreadEntry1, NULL);
    pthread_create(&tid2, NULL, ThreadEntry2, NULL);
    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);

    pthread_cond_destroy(&g_cond);
    pthread_mutex_destroy(&g_lock);
}

为什么等待条件满足的函数要传互斥量?

1、 为什么条件会满足呢? 一定是因为另一个线程改变了共享变量所以条件满足了。既然要改变共享变量,就要保证安全所以这涉及到了加锁解锁。

2、pthread_cond_wait() 1. 释放锁 2. 等待临界资源已经更改到适合本线程进入的信号。
释放锁和等待是原子操作
类似于竞态条件 从释放锁到等待条件成熟 这两步是原子操作 如果不是原子操作 消费者释放锁后线程被切换出去. 生产者生产后发送唤醒消费者的pthread_cond_signal() 这时线程被切回消费者 消费者开始等待条件成熟的信号 可是这个信号生产者已经发送了 消费者收不到了。

生产者消费者模型
1.   一种交易场所
2.   两种角色  生产者消费者。

生产者和生产者 消费者和消费者 是互斥关系
生产者和消费者是同步与互斥关系

服务器的原理: 一个线程负责接收请求 收到后把他放到一个队列中 (生产者) 再来一些消费者线程处理这些请求 然后把它放到下一个队列中。

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

typedef struct Node{
    int data;
    struct Node* next;
}Node;



Node* CreateNode(int value)
{
    Node* new_node = (Node*)malloc(sizeof(Node));
    new_node->data = value;
    new_node->next = NULL;
    return new_node;
}

void Push(Node* head, int value){
    Node* new_node = CreateNode(1);
    new_node->next = head->next;
    head->next = new_node;
}

void Destroy(Node* del){
    free(del);
}

void Pop(Node* head, int value){
    if(head->next == NULL) return;
    Node* del = head->next;
    head->next = head->next->next;
    Destroy(del);
}

void* Init(Node** head){
    *head = CreateNode(-1);
}


Node node;
Node* _head = &node;
Node** head = &_head;

pthread_cond_t g_cond;
pthread_mutex_t g_lock;

void* ThreadEntry2(void* arg)
{
    (void) arg;
    while(1){
        //Node* pNode = CreateNode(1);
        pthread_mutex_lock(&g_lock);
        Push(*head,1 );
        printf("Push 1\n" );
        pthread_cond_signal(&g_cond);
        usleep(5000);
        pthread_mutex_unlock(&g_lock);
    }
    return NULL;
}

void* ThreadEntry1(void* arg)
{
    (void)arg;
    while(1){
        pthread_mutex_lock(&g_lock);
        pthread_cond_wait(&g_cond, &g_lock);
        Pop(*head, 1);
        printf("Pop 1\n");
        usleep(5000);
        pthread_mutex_unlock(&g_lock);
    }
    return NULL;
}

int main()
{
    Init(head);
    pthread_cond_init(&g_cond, NULL);
    pthread_mutex_init(&g_lock, NULL);

    pthread_t tid1, tid2;
    pthread_create(&tid1, NULL, ThreadEntry1, NULL);
    pthread_create(&tid2, NULL, ThreadEntry2, NULL);
    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);

    pthread_cond_destroy(&g_cond)
    pthread_mutex_destroy(&g_lock);

    return  0;
}

这里写图片描述

POSIX信号量

posix信号量比system V 信号量好用, 可用于进程线程间的同步互斥

#include <semaphore.h>

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

sem信号量对象的指针
pshared = 0 表示线程间共享 > 0 表示进程间共享。
value 表示信号量初值。

信号量的 销毁 等待 发出:

int sem_destroy(sem_t *sem);//销毁信号量

int sem_post(sem_t *sem);//信号量加一, 临界资源使用完毕, 发出信号量。
int sem_wait(sem_t *sem);// 信号量  -1,  等待信号量

基于POSIX信号量的固定大小环形队列 需要三个信号量完成:
一个记录有多少个空格,, 一个记录有多少个元素,,一个表示锁。

#include <stdio.h>
#include<unistd.h>
#include<pthread.h>
#include <semaphore.h>

#define QueueMaxSize 1000

typedef struct Queue{
    int data[QueueMaxSize];
    size_t head;
    size_t tail;
    size_t size;
}Queue;

void Init(Queue* q){
    q->head = q->size = q->tail = 0;
}

void QueuePush(Queue* q, int value){
    if(q->size == QueueMaxSize){
        return;
    }
    q->data[q->tail++] = value;
    q->tail %= QueueMaxSize;
    q->size++;
}

void QueuePop(Queue* q, int* value)
{
    if(q->size == 0) return ;
    *value = q->data[q->head++];
    q->head &= (QueueMaxSize - 1);
    q->size--;
}

Queue g_queue;
sem_t g_lock;
sem_t g_data_num;
sem_t g_blank_num;

void* Product(void* arg)
{
    (void)arg;
    int value = 0;
    while(1){
        sem_wait(&g_blank_num);
        sem_wait(&g_lock);
        QueuePush(&g_queue, ++value);
        printf("Product %d\n", value);
        sem_post(&g_lock);
        sem_post(&g_data_num);
        usleep(543211);
    }
    return NULL;
}

void* Consume(void* arg)
{
    (void)arg;
    int value = 0;
    while(1){
        sem_wait(&g_data_num);
        sem_wait(&g_lock);
        QueuePop(&g_queue, &value);
        printf("Consume %d\n", value);
        sem_post(&g_lock);
        sem_post(&g_blank_num);
        usleep(276543);
    }
    return NULL;
}

int main()
{
    sem_init(&g_lock, 0 , 1);
    sem_init(&g_data_num, 0, 0);
    sem_init(&g_blank_num, 0 , QueueMaxSize);
    pthread_t tid1, tid2;
    pthread_create(&tid1, NULL, Product, NULL);
    pthread_create(&tid2, NULL, Consume, NULL);
    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);
    sem_destroy(&g_lock);
    sem


_destroy(&g_data_num);
    sem_destroy(&g_blank_num);
    return 0;
}
读写锁

很多情况下, 多线程读写公共数据 往往是读很多 写很少,读的时候往往伴随着查找工作, 好时长,这个时候加锁往往会降低程序效率。只有采用读写锁。

写独占、、读共享、、写优先级高

读写锁接口:

int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
//定义锁
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,
//解锁
              const pthread_rwlockattr_t *restrict attr);

int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);//锁读
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);//锁写

读写锁:

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

int g_count = 0;

pthread_rwlock_t g_lock;

void* Write(void* arg)
{
    size_t i = (size_t)arg;
    while(1){
        pthread_rwlock_wrlock(&g_lock);
        printf("[%ul] Start Write %d\n",i, g_count);
        g_count++;
        printf("[%ul] Finsh Write %d\n",i, g_count);
        usleep(87654);
        pthread_rwlock_unlock(&g_lock);
    }
    return NULL;
}

void* Read(void* arg)
{
    size_t i = (size_t) arg;
    while(1){
        pthread_rwlock_rdlock(&g_lock);
        printf("[%ul] Start Read %d\n", i, g_count);
        printf("[%ul] Finsh Read %d\n", i, g_count);
        usleep(12345);
        pthread_rwlock_unlock(&g_lock);
    }
    return NULL;
}

int main()
{
    pthread_t tid[8];
    pthread_rwlock_init(&g_lock, NULL);
    size_t i = 0; 
    for(; i < 2; ++i){
        pthread_create(&tid[i], NULL, Write, (void*)i);
    }
    for(i = 2; i < 8; ++i){
        pthread_create(&tid[i], NULL, Read, (void*)i);
    }
    for(i = 0; i < 8 ;++i){
        pthread_join(tid[i], NULL);
    }
    pthread_rwlock_destroy(&g_lock);
    return 0;
}

互斥锁和信号量是 挂起等待锁。
读写锁是自旋锁。

数据在服务器内存中保存,这种情况需要用到读写锁。

C++11线程库
#include <iostream>
#include<cstdio>
#include<unistd.h>
#include<thread>
#include<mutex>//pthread锁  的封装
#include<condition_variable>//pthread 条件变量的封装
#include<atomic>

//class T{
//public:
//    T(const T& ) = delete;
//    T& operator(const T& ) = delete;
//}

//int g_count = 0;
std::atomic_int g_count(0);//

//void ThreadEntry(int x, int y, std::mutex* lock){
//    while(1){
//        //lock->lock();
//        std::lock_guard<std::mutex> guard(*lock);//RAii
//       // if(condition){
//       //     break;
//       //     continue;
//       // }
//        printf("In ThreadEntry %d, %d\n", x, y);
//        //lock->unlock();
//        sleep(1);
//    }
//}

void ThreadEntry(){
    for(int i = 0; i < 50000; ++i){
        ++g_count;
    }
}

int main()
{
    int a = 10;
    int b = 20;
    std::mutex lock;
    //std::thread t1(ThreadEntry, a, b, &lock);//可变参数模板函数 C++11的特性  参数必须是可移动的....
    std::thread t1(ThreadEntry);
    std::thread t2(ThreadEntry);
    //t1.detach();
    t1.join();
    t2.join();
    //printf("g_count = %d\n", g_count);
    std::cout<< "g_count"<<g_count<<std::endl;
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值