美文网首页
线程同步

线程同步

作者: arkliu | 来源:发表于2022-12-04 09:37 被阅读0次

线程同步

多线程访问共享资源的问题

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <ctype.h>

void tfn(int arg) {
    srand(0);
    while(1) {
        printf("HELLO ");
        sleep(rand() % 3);
        printf("WORLD \n");
        sleep(rand() % 3);
    }
}

int main() {
    pthread_t tid;
    pthread_create(&tid, NULL, &tfn, NULL);

    srand(0);
    while(1) {
        printf("hello ");
        sleep(rand() % 3);
        printf("world \n");
        sleep(rand() % 3);
    }
    return 0;
}
err = pthread_create(&ntid, NULL, thr_fn, "new thread: "); 
if (err != 0) {
    fprintf(stderr, "can't create thread: %s\n", strerror(err));
    exit(1);
}   

由于pthread_create的错误码不保存在errno中,因此不能直接用perror(3)打印错误信 息,可以先用strerror(3)把错误码转换成错误信息再打印。

使用pthread_mutex_t实现线程间同步

每个线程在对资源操作前都尝试先加锁,成功加锁才能操作,操作结束解锁
同一时刻,只能有一个线程持有该锁。当 A 线程对某个全局变量加锁访问,B 在访问前尝试加锁,拿不到锁,B 阻塞。C 线程不去加锁,而直接访问该全局变量,依然能够访问,但会出现数据混乱。

互斥锁
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
可以直接 使用宏进行初始化。pthead_mutex_t muetx = PTHREAD_MUTEX_INITIALIZER;

int pthread_mutex_destroy(pthread_mutex_t *mutex); 销毁一个互斥锁
int pthread_mutex_lock(pthread_mutex_t *mutex); 加锁,如果未获得锁,则被阻塞
int pthread_mutex_trylock(pthread_mutex_t *mutex); 尝试加锁
int pthread_mutex_unlock(pthread_mutex_t *mutex); 解锁
以上 5 个函数的返回值都是:成功返回 0, 失败返回错误号

主线程和子线程同时打印hell 和world字样,使用lock确保这两个打印是原子性的

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <ctype.h>

pthread_mutex_t mutex;
void tfn(int arg) {
    srand(0);
    while(1) {
        pthread_mutex_lock(&mutex);
        printf("HELLO ");
        sleep(rand() % 3);
        printf("WORLD \n");
        pthread_mutex_unlock(&mutex);
        sleep(rand() % 3);
    }
}

int main() {
    pthread_t tid;

    pthread_mutex_init(&mutex, NULL);
    pthread_create(&tid, NULL, &tfn, NULL);

    srand(0);
    while(1) {
        pthread_mutex_lock(&mutex);
        printf("hello ");
        sleep(rand() % 3);
        printf("world \n");
        pthread_mutex_unlock(&mutex);
        sleep(rand() % 3);
    }
    pthread_mutex_destroy(&mutex);
    return 0;
}

读写锁

其特性为:写独占,读共享,写锁优先级高
1.读写锁是“写模式加锁”时, 解锁前,所有对该锁加锁的线程都会被阻塞。
2.读写锁是“读模式加锁”时, 如果线程以读模式对其加锁会成功;如果线程以写模式加锁会阻塞。
3.读写锁是“读模式加锁”时, 既有试图以写模式加锁的线程,也有试图以读模式加锁的线程。那么读写锁会阻塞随后的读模式锁请求。优先满足写模式锁。读锁、写锁并行阻塞,写锁优先级高
int pthread_rwlock_init(pthread_rwlock_t *lock, const pthread_rwlockattr_t *attr);
pthread_rwlock_t 读写锁变量,传出参数,attr读写锁相关属性,默认设为NULL即可
int pthread_rwlock_destroy(pthread_rwlock_t *lock);  销毁一个读写锁
int pthread_rwlock_rdlock(pthread_rwlock_t *lock);  读锁
int pthread_rwlock_tryrdlock(pthread_rwlock_t *lock);  尝试加读锁
int pthread_rwlock_wrlock(pthread_rwlock_t *lock);   写锁
int pthread_rwlock_trywrlock(pthread_rwlock_t *lock);  尝试加写锁
int pthread_rwlock_unlock(pthread_rwlock_t *lock);  解锁
以上函数,成功返回0, 失败返回错误号
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <ctype.h>

pthread_rwlock_t lock;
int count;
void tfn_write(int arg) {
    while (1)
    {
        usleep(2000);
        pthread_rwlock_wrlock(&lock); 
        printf("write thread %d global = %d\n", arg, ++count);
        pthread_rwlock_unlock(&lock);
        usleep(3000);
    }
}

void tfn_read(int arg) {
    while (1)
    {
        usleep(2000);
        pthread_rwlock_rdlock(&lock);
        printf("read thread %d global = %d\n", arg, ++count);
        pthread_rwlock_unlock(&lock);
        usleep(3000);
    }
}

int main() {
    pthread_t tid[8];
    pthread_rwlock_init(&lock, NULL);
    for (size_t i = 0; i < 3; i++)
    {
        pthread_create(&tid[i], NULL, &tfn_write, (void *)i);
    }

    for (size_t i = 0; i < 5; i++)
    {
        pthread_create(&tid[i+3], NULL, &tfn_read, (void *)i);
    }
    for (size_t i = 0; i < 8; i++)
    {
        pthread_join(tid[i], NULL);
    }
    
    pthread_rwlock_destroy(&lock);
    return 0;
}

条件变量

int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);
cond 条件变量,传出参数, cond  默认传NULL, 可以用宏来初始化一个默认的条件变量PTHREAD_COND_INITIALIZER
int pthread_cond_destroy(pthread_cond_t *cond);
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
1. 阻塞等待条件变量 cond(参 1)满足
2. 释放已掌握的互斥锁(解锁互斥量)相当于 pthread_mutex_unlock(&mutex); 1.2.两步为一个原子操作。
3. 当被唤醒,pthread_cond_wait 函数返回时,解除阻塞并重新申请获取互斥pthread_mutex_lock(&mutex);
int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex,
         const struct timespec *abstime);
int pthread_cond_signal(pthread_cond_t *cond);
唤醒至少一个阻塞在条件变量上的线程
int pthread_cond_broadcast(pthread_cond_t *cond);
唤醒全部阻塞在条件变量上的线程


动态初始化
pthread_mutex_t mutex;
pthread_mutext_init(&mutex, NULL);

静态初始化
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER

pthread_cond_wait

int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
阻塞等待一个条件变量

  1. 阻塞等待条件变量cond满足条件
  2. 释放已掌握的互斥锁,相当于pthread_mutex_unlock
  3. 当被唤醒,pthread_cond_wait函数返回时,解除阻塞,并重新申请获取互斥锁,pthread_mutex_lock

使用条件变量完成生产者消费者模型

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

struct msg{
    int num;
    struct msg *next;
};
struct msg *head;

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; //定义并且初始化互斥量
pthread_cond_t has_data = PTHREAD_COND_INITIALIZER; // 定义初始化一个条件变量

void* produser(void *arg) {
    while (1)
    {
        struct msg *mp = malloc(sizeof(struct msg));
        mp->num = rand() % 1000 + 1; // 1-1000的随机数
        printf("--produce %d\n", mp->num);

        pthread_mutex_lock(&mutex); // 加锁互斥量
        mp->next = head;  //写公共区域
        head = mp; 
        pthread_mutex_unlock(&mutex); // 解锁互斥量
        pthread_cond_signal(&has_data); // 唤醒阻塞在条件变量has_data上的线程。

        sleep(rand() % 3);
    }
    return (void *)NULL;
}

void* consumer(void *arg) {
    while (1)
    {
        struct msg *mp;
        pthread_mutex_lock(&mutex);
        while (head == NULL)
        {
            // 阻塞等待条件变量
            pthread_cond_wait(&has_data, &mutex);
        }
        mp = head;
        head = mp->next;

        pthread_mutex_unlock(&mutex);

        printf("----------consumer id:%lu   %d\n",pthread_self(), mp->num);
        free(mp);

        sleep(rand() % 3);
    }
    return (void *)NULL;
}

void err_thread(int ret, char *str) {
    if (ret !=0)
    {
        fprintf(stderr, "%s:%s\n", str, strerror(ret));
        pthread_exit(NULL);
    }
}



int main() {
    pthread_t pid, cid;
    int ret;

    srand(time(NULL));

    ret = pthread_create(&pid, NULL, produser, NULL); // 生产者
    if (ret !=0) 
    {
        err_thread(ret, "pthread_create produser");
    }
    ret = pthread_create(&cid, NULL, consumer, NULL); // 消费者
    if (ret !=0) {
        err_thread(ret, "pthread_create consumer");
    }
    ret = pthread_create(&cid, NULL, consumer, NULL); // 消费者
    if (ret !=0) {
        err_thread(ret, "pthread_create consumer");
    }
    ret = pthread_create(&cid, NULL, consumer, NULL); // 消费者
    if (ret !=0) {
        err_thread(ret, "pthread_create consumer");
    }
    pthread_join(pid, NULL);
    pthread_join(cid, NULL);
    return 0;
}

image.png

信号量

由于互斥锁的粒度比较大,如果我们希望在多个线程间对某个对象的部分数据进行共享,使用互斥锁是没有办法实现的,只能将这个数据对象锁住,这样虽然达到了多个线程操作共享数据时保证数据正确定的目的,无形中导致线程的并发性下降,线程从并行执行,变成串行执行,与直接使用单进程无异。

信号量,是相对这种的一种处理方式,既能保证同步,数据不混乱,又能提高线程并发。
主要应用函数

#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);
pshared   0 :用于线程间同步
          1 : 用于进程间同步
value    信号量的初始值,即能够同时访问的线程数



int sem_destroy(sem_t *sem);

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);

以上几个函数都是成功返回0,失败返回-1,同时设置errno

信号量基本操作

sem_wait    1. 信号量>0    则信号量做减1操作
  |            2. 信号量==0, 造成线程阻塞
  |
  |对应
sem_post     将信号量加1,同时唤醒阻塞在信号量上的线程。

因此信号量的初值,决定了占用信号量线程的个数。

信号量同步线程操作

创建三个线程a,b,c,使用信号量,让线程c先运行,然后在运行线程b,最后运行线程a

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

sem_t sem1;
sem_t sem2;

void * a_fun(void *arg) {
    sem_wait(&sem1);
    printf("thread a running\n");
    return (void *)0;
}

void * b_fun(void *arg) {  
    sem_wait(&sem2); 
    printf("thread b running\n");
    //释放线程a
    sem_post(&sem1);
    return (void *)0;
}

void * c_fun(void *arg) {   
    printf("thread c running\n");
    // 释放线程b,对信号量sem2做加1操作,让阻塞的线程b继续运行
    sem_post(&sem2);
    return (void *)0;
}

void err_thread(int ret, char *str) {
    if (ret !=0)
    {
        fprintf(stderr, "%s:%s\n", str, strerror(ret));
        pthread_exit(NULL);
    }
}



int main() {
    pthread_t a_pid, b_pid,c_pid;
    //线程信号量初始化,初始值为0
    sem_init(&sem1, 0,0);
    sem_init(&sem2, 0,0);

    int ret;

    srand(time(NULL));

    ret = pthread_create(&a_pid, NULL, a_fun, NULL); 
    if (ret !=0) 
    {
        err_thread(ret, "pthread_create produser");
    }
    ret = pthread_create(&b_pid, NULL, b_fun, NULL); 
    if (ret !=0) 
    {
        err_thread(ret, "pthread_create produser");
    }
    ret = pthread_create(&c_pid, NULL, c_fun, NULL); 
    if (ret !=0) 
    {
        err_thread(ret, "pthread_create produser");
    }

    pthread_join(a_pid, NULL);
    pthread_join(b_pid, NULL);
    pthread_join(c_pid, NULL);

    //销毁线程信号量
    sem_destroy(&sem1);
    sem_destroy(&sem2);
    return 0;
}

image.png

相关文章

网友评论

      本文标题:线程同步

      本文链接:https://www.haomeiwen.com/subject/boohfdtx.html