Linux多线程(2)

线程

多线程(1)

线程同步与互斥

mutex(互斥量)
大部分情况,线程使用的数据都是局部变量,变量的地址空间在线程栈空间内,这种情况,变量归属单个线程,其他线程无法获得这种变量。
但有时候,很多变量都需要在线程间共享,这样的变量称为共享变量,可以通过数据的共享,完成线程之间的交互。
多个线程并发的操作共享变量,会带来一些问题。

//有问题的售票系统
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sched.h>

int ticket = 103;



void* Buy(void* arg)
{
    char* p = (char*)arg;
    while(1)
    {

       if(ticket > 0)
       {
           usleep(1000);
           --ticket;
           printf(" %s sells ticket %d\n", p, ticket+1);

       }
       else
       {

           break;
       }
    }

}


int main()
{
    pthread_t t1, t2, t3, t4;

  pthread_create(&t1, NULL, Buy, "pthread t1");
  pthread_create(&t2, NULL, Buy, "pthread t2");
  pthread_create(&t3, NULL, Buy, "pthread t3");
  pthread_create(&t4, NULL, Buy, "pthread t4");

  pthread_join(t1, NULL);
  pthread_join(t2, NULL);
  pthread_join(t3, NULL);
  pthread_join(t4, NULL);

    return 0;
}

这里写图片描述

为什么可能无法获得争取结果?
if 语句判断条件为真以后,代码可以并发的切换到其他线程
usleep这个模拟漫长业务的过程,在这个漫长的业务过程中,可能有很多个线程会进入该代码段
–ticket操作本身就不是一个原子操作
操作并不是原子操作,而是对应三条汇编指令:

load:将共享变量ticket从内存加载到寄存器中
update: 更新寄存器里面的值,执行-1操作
store:将新值,从寄存器写回共享变量ticket的内存地址

要解决以上问题,需要做到三点:

  • 代码必须要有互斥行为:当代码进入临界区执行时,不允许其他线程进入该临界区。
  • 如果多个线程同时要求执行临界区的代码,并且临界区没有线程在执行,那么只能允许一个线程进入该临界区。
  • 如果线程不在临界区中执行,那么该线程不能阻止其他线程进入临界区。

要做到这三点,本质上就是需要一把锁。Linux上提供的这把锁叫互斥量。

互斥量的接口

初始化互斥量
初始化互斥量有两种方法:
方法1,静态分配:
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER

方法2,动态分配:
        int pthread_mutex_init(pthread_mutex_t *restrict mutex, 
        const pthread_mutexattr_t *restrict attr);
参数:
mutex:要初始化的互斥量
attr:NULL

销毁互斥量
销毁互斥量需要注意:

使用PTHREAD_ MUTEX_ INITIALIZER初始化的互斥量不需要销毁
不要销毁一个已经加锁的互斥量
已经销毁的互斥量,要确保后面不会有线程再尝试加锁
int pthread_mutex_destroy(pthread_mutex_t *mutex);

互斥量加锁和解锁

int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
返回值:成功返回0,失败返回错误号
调用pthread_ lock 时,可能会遇到以下情况:
互斥量处于未锁状态,该函数会将互斥量锁定,同时返回成功
发起函数调用时,其他线程已经锁定互斥量,或者存在其他线程同时申请互斥量,
但没有竞争到互斥量,那么pthread_ lock调用会陷入阻塞,等待互斥量解锁。
//修改后的售票系统
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sched.h>

int ticket = 103;


pthread_mutex_t lock;

void* Buy(void* arg)
{
    char* p = (char*)arg;
    while(1)
    {
        pthread_mutex_lock(&lock);
       if(ticket > 0)
       {
           usleep(1000);
           --ticket;
           printf(" %s sells ticket %d\n", p, ticket+1);
           pthread_mutex_unlock(&lock);
           usleep(200);
       }
       else
       {
           pthread_mutex_unlock(&lock);
           break;
       }
    }

}

int main()
{
    pthread_t t1, t2, t3, t4;
    pthread_mutex_init(&lock, NULL);

  pthread_create(&t1, NULL, Buy, "pthread t1");
  pthread_create(&t2, NULL, Buy, "pthread t2");
  pthread_create(&t3, NULL, Buy, "pthread t3");
  pthread_create(&t4, NULL, Buy, "pthread t4");

  pthread_join(t1, NULL);
  pthread_join(t2, NULL);
  pthread_join(t3, NULL);
  pthread_join(t4, NULL);
  pthread_mutex_destroy(&lock);

    return 0;
}

这里写图片描述

条件变量
当一个线程互斥地访问某个变量时,它可能发现在其它线程改变状态之前,它什么也做不了。
例如一个线程访问队列时,发现队列为空,它只能等待,只到其它线程将一个节点添加到队列中。这种情况就需要用到条件变量。来等待某个条件发生。

条件变量函数

初始化
int pthread_cond_init(pthread_cond_t *restrict cond,
const pthread_condattr_t *restrict attr);
参数:
cond:要初始化的条件变量
attr:NULL

销毁

int pthread_cond_destroy(pthread_cond_t *cond)
等待条件满足

等待条件满足

int pthread_cond_wait(pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mutex);
参数:
cond:要在这个条件变量上等待
mutex:互斥量,后面详细解释

唤醒等待

int pthread_cond_broadcast(pthread_cond_t *cond);
int pthread_cond_signal(pthread_cond_t *cond);
#include <stdio.h>
#include <pthread.h>

pthread_mutex_t lock;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
void* Run1(void * arg)
{
   while(1)
   {
        pthread_cond_wait(&cond, &lock);
        printf("hello world!\n");
   }
}
void* Run2(void* arg)
{
    while(1)
    {
         pthread_cond_signal(&cond);
         sleep(1);
    }

}

int main()
{
    pthread_t t1, t2;
    pthread_mutex_init(&lock, NULL);
    pthread_create(&t1, NULL, Run1, NULL);
    pthread_create(&t2, NULL, Run2, NULL);

    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    pthread_mutex_destroy(&lock);
    pthread_cond_destroy(&cond);
    return 0;
}

这里写图片描述

为什么pthread_ cond_ wait 需要互斥量?
条件等待是线程间同步的一种手段,如果只有一个线程,条件不满足,一直等下去都不会满足,所以必须要有一个线程通过某些操作,改变共享变量,使原先不满足的条件变得满足,并且友好的通知等待在条件变量上的线程。条件不会无缘无故的突然变得满足了,必然会牵扯到共享数据的变化。所以一定要用互斥锁来保护。没有互斥锁就无法安全的获取和修改共享数据。
按照上面的说法,我们设计出如下的代码:先上锁,发现条件不满足,解锁,然后等待在条件变量上不就行了,如下代码:

// 错误的设计
pthread_mutex_lock(&mutex);
while (condition_is_false) {
pthread_mutex_unlock(&mutex);
//解锁之后,等待之前,条件可能已经满足,信号已经发出,但是该信号可能被错过
pthread_cond_wait(&cond);
pthread_mutex_lock(&mutex);
}
pthread_mutex_unlock(&mutex);

由于解锁和等待不是原子操作。调用解锁之后,pthread_ cond_ wait之前,如果已经有其他线程获取到互斥量,摒弃条件满足,发送了信号,那么pthread_ cond_ wait将错过这个信号,可能会导致线程永远阻塞在这个pthread_ cond_ wait。所以解锁和等待必须是一个原子操作。
int pthread_ cond_ wait(pthread_ cond_ t cond,pthread_ mutex_ t mutex); 进入该函数后,会去看条件量等于0不?等于,就把互斥量变成1,直到cond_ wait返回,把条件量改成1,把互斥量恢复成原样。
条件变量使用规范

等待条件代码
pthread_mutex_lock(&mutex);
while (条件为假)
pthread_cond_wait(cond, mutex);
修改条件
pthread_mutex_unlock(&mutex);
给条件发送信号代码
pthread_mutex_lock(&mutex);
设置条件为真
pthread_cond_signal(cond);
pthread_mutex_unlock(&mutex);

生产者与消费者模型
这里写图片描述

生产者和消费者问题:描述的是生产者生产产品放在缓存区消费着从缓存区消费产品。
要想安全,准确、迅速的执行这个模型,需要满足三个关系。
  • 生产者之间的关系:互斥的(不能两个同时在一个地方生产)
  • 生产者与消费者之间的关系:同步且互斥(不能没生产完就消费,相反,同理。生产完了就应该消费 ,相反,同理)
  • 消费者之间的关系:互斥的(两个不能消费同一个产品)
//使用互斥锁与条件变量实现

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
typedef struct Node     //缓存区为链表
{
    int data;
    struct Node* pnext;
}Node, *pNode;
#define CONSUMER 4
#define PRODUCER 2

pthread_mutex_t mutex;
pthread_cond_t cond;
pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t mutex2 = PTHREAD_MUTEX_INITIALIZER;
pthread_t tids[CONSUMER+PRODUCER];

pNode pHead = NULL;
void InitList(pNode* phead)
{
    (*phead) = (pNode)malloc(sizeof(Node));
    if((*phead) == NULL)
    {
        perror("Malloc");
        return;
    }
    (*phead)->pnext = NULL;
}
void Push(pNode pHead, int data)
{
    pNode temp = (pNode)malloc(sizeof(Node));
    if(temp == NULL)
    {
        perror("malloc");
        return;
    }
    temp->data = data;
    temp->pnext = NULL;
    if(pHead)
    {
       temp->pnext = pHead->pnext;
       pHead->pnext = temp;
    }
    else
    {
       perror("pHead");
       return;
    }

}
void Pop(pNode pHead, int* ret)
{
     if(pHead)
     {
        pNode del = pHead->pnext;
        if(del)
        {
            pHead->pnext = del->pnext;
            if(ret)
                *ret = del->data;
        }
       free(del);
     }
}

void destroy(pNode pHead)
{

    if(pHead)
    {
        while(pHead->pnext)
        {
           Pop(pHead, NULL);
        }
        free(pHead);
    }
}
void show(pNode pHead)
{
    if(pHead)
    {
        pNode pcur = pHead->pnext;
        while(pcur)
        {
           printf("%d ", pcur->data);
           pcur = pcur->pnext;

        }
        printf("\n");

    }
}
void* consumer(void* arg)
{
    int x = 0;
   while(1)
    {
       pthread_mutex_lock(&mutex1);
       pthread_mutex_lock(&mutex);
       if(pHead->pnext == NULL)
       {
          printf("no %d consum wait\n", (int)arg);
          pthread_cond_wait(&cond, &mutex);
       }
          Pop(pHead, &x);
          printf("no %d consum %d\n", (int)arg, x);
          pthread_mutex_unlock(&mutex);
          pthread_mutex_unlock(&mutex1);
          usleep(1000);
    }

}

void* producer(void* arg)
{

    while(1)
    {
       int x = rand()%99 + 1;
       pthread_mutex_lock(&mutex2);
       pthread_mutex_lock(&mutex);
       Push(pHead, x);
       printf("no %d produce %d\n", (int)arg, x);
       pthread_mutex_unlock(&mutex);
       pthread_cond_signal(&cond);
       pthread_mutex_unlock(&mutex2);
       sleep(1);
    }
}
int main()
{
    InitList(&pHead);
    srand(time(NULL));
    pthread_mutex_init(&mutex, NULL);
    pthread_cond_init(&cond, NULL);
    int i = 0;
    for(; i < CONSUMER; ++i)
    {
        pthread_create(&tids[i], NULL, consumer, (void*)i);
    }
    for(i = 0; i < PRODUCER; ++i)
    {
        pthread_create(&tids[CONSUMER+i], NULL, producer, (void*)i);
    }
    for(i = 0; i < sizeof(tids)/sizeof(tids[0]); ++i)
    {
        pthread_join(tids[i], NULL);
    }
    pthread_cond_destroy(&cond);
    pthread_mutex_destroy(&mutex);
    pthread_mutex_destroy(&mutex1);
    pthread_mutex_destroy(&mutex2);
    destroy(pHead);
    return 0;
}

这里写图片描述

POSIX信号量
POSIX信号量和SystemV信号量作用相同,都是用于同步操作,达到无冲突的访问共享资源目的。 但POSIX可以用于线程间同步。
初始化信号量

#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);
参数:
pshared:0表示线程间共享,非零表示进程间共享
value:信号量初始值

销毁信号量

int sem_destroy(sem_t *sem);

等待信号量

功能:等待信号量,会将信号量的值减1
int sem_wait(sem_t *sem);

发布信号量

功能:发布信号量,表示资源使用完毕,可以归还资源了。将信号量值加1。
int sem_post(sem_t *sem);
//互斥锁、信号量实现生产者与消费者模型
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h>
#define CONSUMER 2
#define PRODUCER 2
#define N 10

pthread_mutex_t con_lock =  PTHREAD_MUTEX_INITIALIZER ;
pthread_mutex_t pro_lock =  PTHREAD_MUTEX_INITIALIZER;
sem_t full;
sem_t empty;
int g_buf[N];
int in = 0;
int out = 0;
int product_consum;
int product_produce;
pthread_t tid[CONSUMER + PRODUCER];
void* consum(void *arg)
{
    int i = 0;
    int x = *(int*)arg;
    free(arg);
    while(1)
    {   
       pthread_mutex_lock(&con_lock);    
       printf("consumer%d wait  buf  not empty \n", x); 
       sem_wait(&empty);
       for(i = 0; i < N; ++i)
       {   
    //       printf("i=%02d  ", i);
            if(g_buf[i] == -1) 
            {   
               printf("NULL ");
            }   
            else
            {   
               printf("%d ", g_buf[i]);
            }   
            if(i == out)
            {   
                printf("\t<--consume ");
            }   
       }    
       printf("\n");
       product_consum = g_buf[out];
       printf("consumer:%d  begin consume product %d\n", x, product_consum);
       g_buf[out] = -1; 
       out = (out + 1)%N;
       printf("consumer:%d end consume product %d\n\n", x, product_consum);
       sem_post(&full);
       pthread_mutex_unlock(&con_lock);
       sleep(2);
    }   


}
void* produce(void *arg)
{
    int i = 0;
    int x = *(int*)arg;
    free(arg);
    while(1)
    {   
        pthread_mutex_lock(&pro_lock);
        printf("producer:%d wait buffer not full\n", x); 
        sem_wait(&full);
        for(; i < N; ++i)
        {   
    //       printf("i=%02d  ", i);
            if(g_buf[i] == -1) 
            {   
                printf("NULL ");
            }   
            else
            {   
               printf("%d ", g_buf[i]);
            }   
            if(i == out)
            {   
               printf("\t<--produce ");

            }   
        }   
        printf("\n");
        product_produce = rand()%99 + 1;
        printf("Producer:%d begin produce product %d\n",x, product_produce);    
        g_buf[in] = product_produce;
        in = (in + 1)%N;
        printf("Producer:%d end produce product %d\n\n", x, product_produce);
        sem_post(&empty);
        pthread_mutex_unlock(&pro_lock);
        sleep(1);
    }   
}
int main()
{
    int i = 0;
   for(; i < N; ++i)
   {   
      g_buf[i] = -1; 
   }    
   srand(time(NULL));
   sem_init(&full, 0, N); 
   sem_init(&empty, 0, 0); 
    pthread_t pid[CONSUMER+PRODUCER];
   for(i = 0; i < CONSUMER; ++i)
   {   
        int* p = (int*)malloc(sizeof(int));
        *p = i;
        pthread_create(&tid[i], NULL, consum, (void*)p);
   }   
   for(i = 0; i < PRODUCER; ++i)
   {   
        int* p = (int*)malloc(sizeof(int));
        *p = i;
        pthread_create(&tid[CONSUMER+i], NULL, produce, (void*)p);
   }   

   for(i = 0; i < CONSUMER+PRODUCER; ++i)
   {   
        pthread_join(tid[i], NULL);
   }   
   sem_destroy(&full);
   sem_destroy(&empty);
   pthread_mutex_destroy(&con_lock);
   pthread_mutex_destroy(&pro_lock); 
    return 0;
}

这里写图片描述

读写锁
在编写多线程的时候,有一种情况是十分常见的。那就是,有些公共数据修改的机会比较少。相比较改写,它们读的机会反而高的多。通常而言,在读的过程中,往往伴随着查找的操作,中间耗时很长。给这种代码段加锁,会极大地降低我们程序的效率。那么有没有一种方法,可以专门处理这种多读少写的情况呢? 有,那就是读写锁。读写锁本质上是一种自旋锁。
注意:写独占,读共享,写锁优先级高 //防止一直无法写
//读者与读者没有关系
//读者与写者之间是互斥的
//写者与写者之间互斥
读写锁接口
初始化

    int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,
    const pthread_rwlockattr_t *restrict attr);

销毁

int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);

加锁和解锁

int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
#include <stdio.h>
#include <pthread.h>

#define Reader 5
#define Writer 3


pthread_t tid[Reader+Writer];
int count = 0;
pthread_rwlock_t rwlock;

void* route_write(void* arg)
{
    int temp;
    while(1)
    {
        temp = count;
        pthread_rwlock_wrlock(&rwlock);
        printf("Witer:%d :%#x:pthread count = %d  ++count = %d\n",(int)arg,\
                        pthread_self(),temp, ++count);
        pthread_rwlock_unlock(&rwlock);
        usleep(1000);
    }

}
void* route_read(void* arg)
{
    while(1)
    {
        pthread_rwlock_rdlock(&rwlock);
        printf("Reader:%d :%#x pthread count = %d\n",(int)arg,\
                        pthread_self(), count);
        pthread_rwlock_unlock(&rwlock);
        usleep(1000);

    }

}
int main()
{
    int i = 0;
    pthread_rwlock_init(&rwlock, NULL);
    for(; i < Reader; ++i)
    {
        pthread_create(&tid[i], NULL, route_read, (void*)i);
    }
   for(i = 0; i < Writer; ++i)
   {
        pthread_create(&tid[Reader+i], NULL, route_write, (void*)i);
   }
   for(i = 0; i < Reader+Writer; ++i)
   {
        pthread_join(tid[i], NULL);
   }
    pthread_rwlock_destroy(&rwlock);
    return 0;
}

这里写图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值