多线程编程(下)

11.互斥锁
静态初始化
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;    进程结束才释放内存单元
动态初始化
pthread_mutex_t mutex; //定义互斥锁对象
pthread_mutex_init(&mutex, NULL); // 分配内核资源
...
pthread_mutex_destroy(&mutex); // 释放内核资源
...
pthread_mutex_lock(&mutex); // 锁定互斥锁
任何时刻只会有一个线程对特定的互斥锁锁定成功,其它试图对其锁定的线程会在此函数中阻塞等待,直到该互斥锁的持有者线程将其解锁为止。
pthread_mutex_unlock(&mutex); // 解锁互斥锁
对特定互斥锁对象锁定成功的线程通过此函数将其解锁,那些阻塞于对该互斥锁对象试图锁定的线程中的一个会被唤醒,得到该互斥锁,并从pthread_mutex_lock函数中返回。
void* thread1(void* arg) {
    ...
    pthread_mutex_lock(&mutex);
    执行操作,访问共享对象
    pthread_mutex_unlock(&mutex);
    ...
}
void* thread2(void* arg) {
    ...
    pthread_mutex_lock(&mutex);
    执行操作,访问共享对象
    pthread_mutex_unlock(&mutex);
    ...
}
代码:mutex1.c、mutex2.c

/* mutex1.c */
#include <stdio.h>
#include <string.h>
#include <pthread.h>
unsigned int g = 0;
//pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t m;
void* thread_proc(void* arg) {
    //pthread_mutex_lock(&m);
    for (unsigned int i = 0; i < 100000000; ++i){
        pthread_mutex_lock(&m);
        ++g;
        pthread_mutex_unlock(&m);
    }
    //pthread_mutex_unlock(&m);
    return NULL;
}
int main(void) {
    pthread_mutex_init(&m, NULL);
    pthread_t t1, t2;
    pthread_create(&t1, NULL, thread_proc, NULL);
    pthread_create(&t2, NULL, thread_proc, NULL);
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    pthread_mutex_destroy(&m);
    printf("g = %u\n", g);
    return 0;
}


/* mutex2.c */
#include <stdio.h>
#include <string.h>
#include <pthread.h>
int g = 0;
//pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t m;
void* thread_proc1(void* arg) {
    for (unsigned int i = 0; i < 10000000; ++i){
        pthread_mutex_lock(&m);
        ++g;
        pthread_mutex_unlock(&m);
    }
    return NULL;
}
void* thread_proc2(void* arg) {
    for (unsigned int i = 0; i < 10000000; ++i){
        pthread_mutex_lock(&m);
        --g;
        pthread_mutex_unlock(&m);
    }
    return NULL;
}
int main(void) {
    pthread_mutex_init(&m, NULL);
    pthread_t t1, t2;
    pthread_create(&t1, NULL, thread_proc1, NULL);
    pthread_create(&t2, NULL, thread_proc2, NULL);
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    pthread_mutex_destroy(&m);
    printf("g = %d\n", g);
    return 0;
}

互斥锁在保证数据一致性和防止并发冲突的同时,也牺牲了多线程应用的并发性,因此在设计上,应尽量少地使用互斥锁,或者说只有在需要被互斥锁保护的操作过程中,使用它,而对于保护需求不明显的场合尽量不用。

12.信号量
功能和用法与XSI/IPC对象中的信号量集非常类似,但是XSI/IPC对象中的信号量集只能用于进程,而这里的信号量既可以用于进程,也可以用于线程。
#include <semphore.h>
初始化信号量
int sem_init(sem_t* sem, int pshared, unsigned int value);
成功返回0,失败返回-1。
sem - 信号量。
pshared - 用0表示该信号量用于线程,非0表示该信号量用于进程。
value - 信号量的初值,即初始空闲资源数。
当pshared参数取0,信号量仅用于一个进程中的多个线程,其本质就是一个普通的全局变量,但如果改参数取值为非0,则信号量可用于不同的进程,其存储位置在可为这些进程所访问的共享内存中。
销毁信号量
int sem_destroy(sem_t* sem);
成功返回0,失败返回-1。
sem - 信号量。
等待信号量
int sem_wait(sem_t* sem);
成功返回0,失败返回-1。
如果信号量的当前值大于0,则将其减1并立即返回0,表示获得资源。如果该信号量的当前值等于0,这就意味着已经没有空闲资源可供分配,调用该该函数的线程或进程将被阻塞,直到因资源被释放信号量的值够减1为止。
释放信号量
int sem_post(sem_t* sem);
成功返回0,失败返回-1。
将信号量的值加1。那些正阻塞于针对该信号量的sem_wait函数调用中的一个线程或进程将被唤醒,并在对信号量减1之后从sem_wait函数中返回。
获取信号量的当前值
int sem_getvalue(sem_t* sem, int* sval);
成功返回0,失败返回-1。
sem - 信号量
sval - 输出信号量的当前值,即当前可分配资源数
代码:sem.c

/* sem.c */
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
#define MAX_CONNS  5 // 最大连接数
#define MAX_USERS 50 // 最大用户数
sem_t s;
void* thread_user(void* arg) {
    pthread_t tid = pthread_self();
    int sval;
    sem_getvalue(&s, &sval);
    printf("%lu线程:等待数据连接(还剩%d个空闲连接)...\n", tid, sval);
    sem_wait(&s);
    sem_getvalue(&s, &sval);
    printf("%lu线程:获得数据连接(还剩%d个空闲连接)...\n", tid, sval);
    usleep(1000000);
    sem_post(&s);
    sem_getvalue(&s, &sval);
    printf("%lu线程:释放数据连接(还剩%d个空闲连接)...\n", tid, sval);
    return NULL;
}
int main(void) {
    pthread_t tids[MAX_USERS];
    sem_init(&s, 0, MAX_CONNS);
    for (int i = 0; i < sizeof(tids) / sizeof(tids[0]); ++i)
        pthread_create(&tids[i], NULL, thread_user, NULL);
    for (int i = 0; i < sizeof(tids) / sizeof(tids[0]); ++i)
        pthread_join(tids[i], NULL);
    sem_destroy(&s);
    return 0;
}

互斥锁可以看作是初值为1的信号量

13.死锁
代码:dl.c

/* dl.c */
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
pthread_mutex_t a = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t b = PTHREAD_MUTEX_INITIALIZER;
void* thread_proc1(void* arg) {
    printf("线程1:加锁a...\n");
    pthread_mutex_lock(&a);
    printf("线程1:加锁a成功!\n");
    sleep(1);
    printf("线程1:加锁b...\n");
    pthread_mutex_lock(&b);
    printf("线程1:加锁b成功!\n");
    // ...
    pthread_mutex_unlock(&b);
    printf("线程1:解锁b。\n");
    pthread_mutex_unlock(&a);
    printf("线程1:解锁a。\n");
    return NULL;
}
void* thread_proc2(void* arg) {
    printf("线程2:加锁b...\n");
    pthread_mutex_lock(&b);
    printf("线程2:加锁b成功!\n");
    sleep(1);
    printf("线程2:加锁a...\n");
    pthread_mutex_lock(&a);
    printf("线程2:加锁a成功!\n");
    // ...
    pthread_mutex_unlock(&a);
    printf("线程2:解锁a。\n");
    pthread_mutex_unlock(&b);
    printf("线程2:解锁b。\n");
    return NULL;
}
int main(void) {
    pthread_t t1, t2;
    pthread_create(&t1, NULL, thread_proc1, NULL);
    pthread_create(&t2, NULL, thread_proc2, NULL);
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    printf("完成!\n");
    return 0;
}

死锁的四个必要条件
1)独占排它:线程以独占的方式使用其所获得的资源,即在一个段时间内不允许其它线程使用该资源。这段时间内,任何试图请求该资源的线程只能在阻塞中等待,直到资源被其拥有者主动释放。
2)请求保持:线程已经拥有了至少一份资源,但又试图获取已被其它线程拥有的资源,因此只能在阻塞中等待,同时对自己已经获取的资源又坚守不放。
3)不可剥夺:线程已经获得的资源,在其未被使用完之前,不可被强制剥夺,而只能由其拥有者自己释放。
4)循环等待:线程集合{T0,T1,Tn}中,T0等待T1占有的资源,T1等待T2占有的资源,...,Tn等待T0占有的资源,形成环路。

14.条件变量
线程1
    ...
    if (后续操作所依赖的条件不满足)
        睡眠于条件变量;
    后续操作
    ...
线程2
    ...
    创建出后续操作所依赖的条件
    唤醒在条件变量中睡眠的线程
    ...
静态初始化
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
动态初始化
pthread_cond_init(&cond, NULL);
...
pthread_cond_destroy(&cond);
等待条件变量,即在条件变量中睡眠
int pthread_cond_wait(pthread_cond_t* cond, pthread_mutex_t* mutex);
当调用线程在cond条件变量中睡眠期间mutex互斥会被解锁,直到该线程从条件变量中苏醒,即从该函数中返回再重新拥有该互斥锁。
int pthread_cond_signal(pthread_cond_t* cond);
唤醒在条件变量cond中睡眠的第一个线程,即条件等待队列的队首线程,待其重新获得互斥锁以后从pthread_cond_wait函数中返回。
int pthread_cond_broadcast(pthread_cond_t* cond);
唤醒在条件变量cond中睡眠的所有线程,只有重新获得互斥锁的线程会从pthread_cond_wait函数中返回。

生产者——消费者模型:
硬件/网络->生产者线程-数据->消费者线程
                             \__________________/
                                        刚性耦合
这种情况生产者线程速率会影响消费者线程速率,导致整个线程执行速率下降,可通过柔性耦合解决。

硬件/网络->生产者线程-数据->缓冲区-数据->消费者线程
                             \________________________________/
                                              柔性耦合
理想缓冲区:永远不满且不空
实际缓冲区:满不能放入——撑死,空不能提取——饿死。

生产者线程:
    if (缓冲区满)
        睡入非满条件变量,释放缓冲区锁
        被唤醒后,离开条件等候队列,重新获得缓冲区锁
    生产->非空
    唤醒在非空条件变量中睡眠的消费者线程
消费者线程:
    if (缓冲区空)
        睡入非空条件变量,释放缓冲区锁
        被唤醒后,离开条件等候队列,重新获得缓冲区锁
    消费->非满
    唤醒在非满条件变量中睡眠的生产者线程
代码:cond.c

/* cond.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#define MAX_STOCK 5 // 仓库容量
char storage[MAX_STOCK]; // 仓库
int stock = 0; // 当前库存
pthread_mutex_t mutex =PTHREAD_MUTEX_INITIALIZER;
// 非满条件变量
pthread_cond_t full = PTHREAD_COND_INITIALIZER;
// 非空条件变量
pthread_cond_t empty = PTHREAD_COND_INITIALIZER;
// 显示库存
void show(char const* who, char const* op,
    char prod) {
    printf("%s:", who);
    for (int i = 0; i < stock; ++i)
        printf("%c", storage[i]);
    printf("%s%c\n", op, prod);
}
// 生产者线程
void* producer(void* arg) {
    char const* who = (char const*)arg;
    for (;;) {
        pthread_mutex_lock(&mutex);
        while (stock == MAX_STOCK) {
            printf("\033[;;32m%s:满仓!\033[0m\n", who);
            pthread_cond_wait(&full, &mutex);
        }
        char prod = 'A' + rand() % 26;
        show(who, "<-", prod);
        storage[stock++] = prod;
        //pthread_cond_signal(&empty);
        pthread_cond_broadcast(&empty);
        pthread_mutex_unlock(&mutex);
        usleep((rand() % 100) * 1000);
    }
    return NULL;
}
// 消费者线程
void* customer(void* arg) {
    char const* who = (char const*)arg;
    for (;;) {
        pthread_mutex_lock(&mutex);
        while (stock == 0) {
            printf("\033[;;31m%s:空仓!\033[0m\n", who);
            pthread_cond_wait(&empty, &mutex);
        }
        char prod = storage[--stock];
        show(who, "->", prod);
        //pthread_cond_signal(&full);
        pthread_cond_broadcast(&full);
        pthread_mutex_unlock(&mutex);
        usleep((rand() % 100) * 1000);
    }
    return NULL;
}
int main(void) {
    srand(time(NULL));
    pthread_attr_t attr;
    pthread_attr_init(&attr);
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
    pthread_t tid;
    pthread_create(&tid, &attr, producer, "生产者1");
    pthread_create(&tid, &attr, producer, "生产者2");
    pthread_create(&tid, &attr, customer, "消费者1");
    pthread_create(&tid, &attr, customer, "消费者2");
    getchar();
    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

暗里い着迷

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值