Linux系统编程之线程同步

本文详细介绍了多线程编程中的同步机制,包括互斥量、读写锁、条件变量和信号量的使用方法。通过实例代码展示了如何初始化、操作和销毁这些同步工具,以及它们在进程间通信中的应用,如使用文件锁。这些技术有助于提高程序的并发性和资源管理效率。
摘要由CSDN通过智能技术生成

1. 互斥量操作函数

pthread_mutex_t --> 锁类型

初始化:

int pthread_mutex_init(pthread_mutex_t* restrict mutex, const pthread_mutexattr_t* restrict attr);

attr一般传入NULL。

int pthread_mutex_destroy(pthread_mutex_t* mutex);

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

加锁与释放锁:

int pthread_mutex_lock(pthread_mutex_t* mutex);

int pthread_mutex_unlock(pthread_mutex_t* mutex);

int pthread_mutex_trylock(pthread_mutex_t* mutex); --> 已被其它线程锁定时返回非0值,获得锁成功返回0

当trylock没有拿到锁时,放弃已经获得的锁,避免多个锁引起的死锁。

2. 读写锁:与互斥量类似,但读写锁允许更高的并行性。其特性为:写独占,读共享。适用于对数据读的次数远大于写的次数时

一把读写锁具备三种状态:读模式下加锁状态(读锁);写模式下加锁状态(写锁);不加锁状态。

机制

写者:写者使用写锁,如果当前没有读者,也没有其它写者,写者立即获得写锁;否则写者将等待,直到没有读者和写者;

读者:读者使用读锁,如果当前没有写者,读者立即获得读锁;否则读者等待,直到没有写者。

特性

(1)同一时刻只有一个线程可以获得写锁,同一时刻可以有多个线程获得读锁;

(2)读写锁处于写锁状态时,所有试图对读写锁加锁的线程,不管是读者试图加读锁还是写者试图加写锁,都会被阻塞;

(3)读写锁处于读锁状态时,有写者试图加写锁时,之后的其它线程的读锁请求会被阻塞,以避免写者长时间的不写入。只要争夺锁的中间有写锁,其它锁都要让写锁优先,就算是当前状态是读锁

对应函数:成功返回0,失败返回错误号

类型:pthread_rwlock_t

int pthread_rwlock_init(pthread_rwlock_t* restrict relock, const pthread_relockattr_t* attr);

int pthread_rwlock_destroy(othread_rwlock_t* rwlock);

int pthread_rwlock_wrlock(pthread_rwlock_t* rwlock);

int pthread_rwlock_trywrlock(pthread_rwlock_t* rwlock);

int pthread_rwlock_rdlock(pthread_rwlock_t* rwlock);

int pthread_rwlock_tryrdlock(pthread_rwlock_t* rwlock);

int pthread_rwlock_unlock(pthread_rwlock_t* rwlock);

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

pthread_rwlock_t rwlock;
int g_val = 10;

void* thread_rd(void* arg)
{
    while(1)
    {
        pthread_rwlock_rdlock(&rwlock);
        printf("Thread read: tid = %lu, g_val = %d\n", pthread_self(), g_val);
        pthread_rwlock_unlock(&rwlock);
        usleep(900);
    }
    pthread_exit(NULL);
}

void* thread_wr(void* arg)
{
    while(1)
    {
        usleep(900);
        pthread_rwlock_wrlock(&rwlock);
        printf("Thread write: tid = %lu, g_val = %d, changed to %d\n", pthread_self(), g_val, ++g_val);
        pthread_rwlock_unlock(&rwlock);
    }
    pthread_exit(NULL);
}

int main(void)
{
    pthread_rwlock_init(&rwlock, NULL);

    pthread_t tids[8];
    int ret;
    for (int  i = 0; i < 3; ++i)
    {
        if ((ret = pthread_create(&tids[i], NULL, thread_rd, NULL)) != 0)
        {
            fprintf(stderr, "pthread_create error: %s\n", strerror(ret));
            exit(1);
        }
    }

    for (int i = 3; i < 8; ++i)
    {
        if ((ret = pthread_create(&tids[i], NULL, thread_wr, NULL)) != 0)
        {
            fprintf(stderr, "pthread_create error: %s\n", strerror(ret));
            exit(1);
        }
    }
    
    sleep(15);
    for (int i = 0; i < 8; ++i)
        pthread_cancel(tids[i]);

    pthread_rwlock_destroy(&rwlock);

    return 0;
}

3. 条件变量:本身不是锁,但它也可以造成线程阻塞,通常会与互斥锁配合。可以减少竞争,降低CPU使用率(阻塞时会主动放弃CPU)

类型:pthread_cond_t;

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

int pthread_cond_destroy(pthread_cond_t* cond);

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

pthread_cond_wait: 阻塞等待一个条件变量

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

int pthread_cond_timedwait(pthread_cond_t* restrict cond, pthread_mutex_t* restrict mutex, const struct timespec* restrict abstime);

函数作用

(1)阻塞等待条件变量cond满足;

(2)释放已经掌握的互斥锁(解锁互斥量),相当于调用pthread_mutex_unlock(&mutex);

         (1)与(2)是一个原子操作, 所以在调用wait之前,需要先初始化一个mutex,并且先加锁

(3)当被唤醒,pthread_cond_wait函数返回时,解除阻塞并重新申请获得互斥量mutex,相当于pthread_mutex_lock(&mutex);

int pthread_cond_signal(pthread_cond_t* cond);  --> unblock at least one of the threads that are blocked on the cond;

int pthread_cond_broadcast(pthread_cond_t* cond); --> unblock all.

4. 信号量sem  <semaphore.h>

进化版的互斥量(1 --> N)

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

信号量是相对折中的处理方式,既能保证同步,数据不混乱,又能提高线程并发。

类型:sem_t

返回值:成功返回0,失败返回-1同时设置errno

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

pshared --> indicates whether this semaphore is to be shared between  the  threads  of  a process, or between processes.

                   If  pshared  has  the  value 0, then the semaphore is shared between the threads of a process, and should be located at some address that is visible to all threads (e.g., a global  variable,  or  a
                   variable allocated dynamically on the heap).

                   If  pshared is nonzero, then the semaphore is shared between processes, and should be located in a region of shared memory (see shm_open(3), mmap(2), and shmget(2)).   (Since  a  child                            created  by fork(2)  inherits  its  parent's  memory mappings, it can also access the semaphore.)  Any process that can access the  shared  memory  region  can  operate  on  the  semaphore  using                    sem_post(3), sem_wait(3), and so on.

int sem_destroy(sem_t* sem);

int sem_wait(sem_t* sem);  int sem_trywait(sem_t* sem); int sem_timedwait(sem_t* sem);

int sem_post(sem_t* sem);

sem_wait  --> 当信号量大于0,则信号量--; 若信号量等于0,则造成线程阻塞

sem_post --> 将信号量++,同时唤醒阻塞在信号量上的线程。

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

5. 进程间通信

(1)互斥量mutex --> 指定属性pthread_mutexattr_t

int pthread_mutexattr_init(pthread_mutexattr_t* attr);

int pthread_mutexattr_destroy(pthread_mutexattr_t* attr);

int pthread_mutexattr_setpshared(pthread_mutexattr_t* attr, int pshared);

pshared取值: 线程锁:PTHREAD_PROCESS_PRIVATE(mutex默认属性即为线程锁,进程间私有)

                        进程锁:PTHREAD_PROCESS_SHARED

#include <unistd.h>
#include <sys/mman.h>
#include <stdio.h>
#include <sys/types.h>
#include <stdlib.h>
#include <pthread.h>
#include <string.h>
#include <sys/wait.h>

typedef struct
{
    int m_val;
    pthread_mutex_t m_mutex;
    pthread_mutexattr_t m_mattr; 
} protectedVal;

int main(void)
{
    protectedVal* mm = mmap(NULL, sizeof(protectedVal), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
    if (mm == MAP_FAILED)
    {
        perror("mmap error");
        exit(1);
    }

    memset(mm, 0, sizeof(protectedVal));
    pthread_mutexattr_init(&mm->m_mattr);
    pthread_mutexattr_setpshared(&mm->m_mattr, PTHREAD_PROCESS_SHARED);
    pthread_mutex_init(&mm->m_mutex, &mm->m_mattr);

    pid_t pid;
    if ((pid = fork()) == 0)
    {
        // 子进程
        for (int i = 0; i < 10; ++i)
        {
            pthread_mutex_lock(&mm->m_mutex);
            mm->m_val++;
            printf("Child process: pid = %d, val = %d\n", getpid(), mm->m_val);
            pthread_mutex_unlock(&mm->m_mutex);
            sleep(1);
        }
    }
    else if (pid > 0)
    {
        // 父进程
        for (int i = 0; i < 10; ++i)
        {
            pthread_mutex_lock(&mm->m_mutex);
            mm->m_val += 2;
            printf("Parent process: pid = %d, val = %d.\n", getpid(), mm->m_val);
            pthread_mutex_unlock(&mm->m_mutex);
            sleep(1);
        }
        if (waitpid(pid, NULL, 0) == -1)
        {
            perror("waitpid error");
            exit(1);
        }
    }

    else
    {
        perror("fork error");
        exit(1);
    }

    if (munmap(mm, sizeof(protectedVal)) == -1)
    {
        perror("munmap failed");
        exit(1);
    }

    return 0;
}

(2) 文件锁:借助fcntl函数来实现锁机制。操作文件的进程没有获得锁时,可以打开,但无法执行read、write操作。

fcntl函数:获取、设置文件访问控制属性

int fcntl(int fd, int cmd, .../* arg */);

参2:

F_SETLK(参3struct flock*)设置文件锁(trylock)

F_SETLKW(struct flock*) 设置文件锁(lock)  W --> wait

F_GETLK(struct flock*)获取文件锁

struct flock

{

...

short I_type;  锁的类型:F_RDLCK、F_WRLCK、F_UNLCK

short I_whence; 偏移位置:SEEK_SET、SEEK_CUR、SEEK_END

off_t I_start;  起始偏移

off_t l_len;  长度

pid_t l_pid;  持有该锁的进程ID(F_GETLK only)

...

};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值