只有脚踩上去才知其远近和曲折

多线程与多进程

进程和线程

定义:

进程是具有一定独立功能的某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位,当一个进程内有多个线程时,线程的程序是其所属进程的一部分

,表示进程中的一个控制点,执行一系列的指令,同属于一个进程的其他线程共享进程所拥有的全部资源(包括地址空间)它是比进程更小的能独立运行的基本单位,线程自己基本上不拥有系统资源,只拥有一旦运行中比不可少的资源,(如程序计数器,一组寄存器和栈)他的创建,撤销,切换所需要的时空开销比进程要小。可进一步提高系统的并发性

区别:

进程有独立的地址空间,一个进程崩溃后不会影响其他的进程,线程只有一个进程中不同执行路劲。线程有自己的堆栈和局部变量,但进程之间没有独立的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程程序健壮,但是进程切换时,资源的耗费交大,效率低,但对于一些要求工会进行并且有要共享某些变量的并发操作,智能用线程,不能用进程

用户级线程:

它仅存在于用户中,折中线程是不依赖与操作系统内核心的,引用进程利用线程库来完成其创建,同步,调度和管理线程,因此用户线程间的切换不需要内核特权,不需要用户态,核心态切换,速度快,操作系统内核无法感知用户级线程的存在。

优点:

  1. 线程的调度不需要内核直接参与,控制简单
  2. 可以在不支持线程的操作系统中实现
  3. 创建和销毁线程,线程切换代价等线程管理
  4. 允许每个进程制定自己的调度算法,线程管理灵活,这就是不许自己写管理程序,与内个线程的区别
  5. 线程能够利用表空间和堆空间比内核级线程多
  6. 同一进程只能同时有一个线程在运行,如果有一个线程使用了系统调用而阻塞,那么整个进程都会被挂起,另外,页面失效也会产生同样的问题
  7. 资源调度按照进程进行,多个处理机下,同一个进程中的线程只能在同一个处理机下分时复用

内核级线程:

这类线程依赖与内核,又称为内核支持的线程或轻量级进程,无论是在用户程序中的线程还是在系统进程中的线程,他们的创建,撤销和切换都由内核实现,为此,需要在内核中建立一个线程控制块,内核根据该控制块而感知该线程的存在并对线程进行控制

优点:

  1. 当有多个处理机时,一个进程的多个线程可同是执行
  2. 由内个进行调度

共享内存

共享内存就是映射一段能能被机器进程所访问的内存,这段内存共享一个由进程创建,但多个进程都可以访问,共享内存是最快的ipc方式,它是针对其他进程间通信方式运行效率低而专门设计的,它往往与其他通信机制,如信号量,配合使用,来实现进程间的同步和通信

所谓共享内存,就是多个进程间共同地使用同一段物理内存空间,它是通过将同一段物理内存映射到不同进程的 虚拟空间来实现的。由于映射到不同进程的虚拟空间中,不同进程可以直接使用,不需要像消息队列那样进行复制,所以共享内存的效率很高。共享内存可以通过mmap()映射普通文件机制来实现,也可以System V共享内存机制来实现,System V是通过映射特殊文件系统shm中的文件实现进程间的共享内存通信,也就是说每个共享内存区域对应特殊文件系统shm中的一个文件。

实现

多进程实现

https://www.cnblogs.com/tgycoder/p/5263644.html

在Linux下产生新的进程的系统调用就是fork函数。fork是最难理解的概念之一:它执行一次却返回两个值。fork函数是Unix系统最杰出的成就之一,它是七十年代UNIX早期的开发者经过长期在理论和实践上的艰苦探索后取得的成果,一方面,它使操作系统在进程管理上付出了最小的代价,另一方面,又为程序员提供了一个简洁明了的多进程方法。与DOS和早期的Windows不同,Unix/Linux系统是真正实现多任务操作的系统,可以说,不使用多进程编程,就不能算是真正的Linux环境下编程。

void main(){
    int i;
    if (fork() == 0) {
        /* 子进程程序 */
        for (i = 1; i <1000; i++)
            printf("This is child process\n");
    }
    else {
        /* 父进程程序*/
        for (i = 1; i <1000; i++)
            printf("This is parent process\n");
    }
}

#### 多线程实现

https://www.cnblogs.com/luoxn28/p/6087649.html

线程控制函数
pthread_create

#include <pthread.h>
int pthread_create(pthread_t * tidp, const pthread_attr_t *attr, void (start_rtn)(void *), void *arg);
    // 返回:成功返回0,出错返回错误编号

  当pthread_create函数返回成功时,有tidp指向的内存被设置为新创建线程的线程ID,其类型pthread_t定义为:

#include <bits/pthreadtypes.h>
typedef unsigned long int pthread_t;

  attr参数用于设置各种不同的线程属性,为NULL时表示默认线程属性。新创建的线程从start_rtn函数的地址开始运行,该函数只有一个无类型指针的参数arg,如果需要向start_rtn函数传入的参数不止一个,可以把参数放入到一个结构中,然后把这个结构的地址作为arg的参数传入。

  线程创建时并不能保证哪个线程会先运行:是新创建的线程还是调用线程。新创建的线程可以访问调用进程的地址空间,并且继承调用线程的浮点环境和信号屏蔽字,但是该线程的未决信号集被清除。那什么是未决信号呢,信号产生到信号被处理这段时间间隔,称信号是未决的。

pthread_exit

#include <pthread.h>
void pthread_exit(void *rval_ptr);
    // 线程终止

  线程在结束时最好调用该函数,以确保安全、干净的退出。pthread_exit函数通过rval_ptr参数向调用线程的回收者传递退出信息,进程中的其他线程可以调用pthread_join函数访问到这个指针。pthread_exit执行完后不会返回到调用者,而且永远不会失败。

线程可以通过以下三种方式退出,在不终止整个进程的情况下停止它的控制流:

  • 线程只是从启动过程中退出,返回值是线程的退出码
  • 线程可以被同一进程中的其他线程取消
  • 线程调用pthread_exit

pthread_join

#include <pthread.h>
int pthread_join(pthread_t thread, void **rval_ptr);
    // 返回:成功返回0,出错返回错误代码

  thread是目标线程标识符,rval_ptr指向目标线程返回时的退出信息,该函数会一直阻塞,直到被回收的线程结束为止。可能的错误码为:

pthread_cancel

#include <pthread.h>
int pthread_cancel(pthread_t thread);
    // 返回:成功返回0,出错返回错误代码

  默认情况下,pthread_cancel函数会使有thread标识的线程的表现为如同调用了参数为PTHREAD_CANCEL的pthread_exit函数,但是,接收到取消请求的目标线程可以决定是否允许被取消以及如何取消,这分别由以下两个函数来控制:

#include <pthread.h>
int pthread_setcancelstate(int state, int *oldstate);
int pthread_setcanceltype(int type, int *oldstate);

  注意pthread_cancel并不等待线程结束,它只是提出请求

互斥锁

  互斥量本质是一把锁,在访问公共资源前对互斥量设置(加锁),确保同一时间只有一个线程访问数据,在访问完成后再释放(解锁)互斥量。在互斥量加锁之后,其他线程试图对该互斥量再次加锁时都会被阻塞,知道当前线程释放互斥锁。如果释放互斥量时有一个以上的互斥量,那么所有在该互斥量上阻塞的线程都会变成可运行状态,第一个变成运行的线程可以对互斥量加锁,其他线程看到互斥量依然是锁着的,只能再次阻塞等待该互斥量。

  互斥量用pthread_mutex_t数据类型表示,在使用互斥量之前,必须使用pthread_mutex_init函数对它进行初始化,注意,使用完毕后需调用pthread_mutex_destroy。

#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr);
int pthread_mutex_destroy(pthread_mutex_t *mutex);
    // 两个函数返回值,成功返回0,否则返回错误码

  pthread_mutex_init用于初始化互斥锁,mutexattr用于指定互斥锁的属性,若为NULL,则表示默认属性。除了用这个函数初始化互斥所外,还可以用如下方式初始化:pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER。
  pthread_mutex_destroy用于销毁互斥锁,以释放占用的内核资源,销毁一个已经加锁的互斥锁将导致不可预期的后果。

#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
    // 成功返回0,否则返回错误码

  pthread_mutex_lock以原子操作给一个互斥锁加锁。如果目标互斥锁已经被加锁,则pthread_mutex_lock则被阻塞,直到该互斥锁占有者把它给解锁。
  pthread_mutex_trylock和pthread_mutex_lock类似,不过它始终立即返回,而不论被操作的互斥锁是否加锁,是pthread_mutex_lock的非阻塞版本。当目标互斥锁未被加锁时,pthread_mutex_trylock进行加锁操作;否则将返回EBUSY错误码。注意:这里讨论的pthread_mutex_lock和pthread_mutex_trylock是针对普通锁而言的,对于其他类型的锁,这两个加锁函数会有不同的行为。
  pthread_mutex_unlock以原子操作方式给一个互斥锁进行解锁操作。如果此时有其他线程正在等待这个互斥锁,则这些线程中的一个将获得它。

互斥锁使用示例:

/**
 * 使用3个线程分别打印 A B C
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>

pthread_mutex_t g_mutex;
int g_cnt = 0;

void *func(void *arg)
{
    int loop = 3;
    int result = (int)arg;

    while (loop > 0) {
        if (g_cnt % 3 == result) {
            switch (result)
            {
                case 0: {
                    printf("--- a\n");
                    break;
                }
                case 1: {
                    printf("--- b\n");
                    break;
                }
                case 2: {
                    printf("--- c\n");
                    break;
                }
                default: {
                    return NULL;
                }
            }

            pthread_mutex_lock(&g_mutex);
            g_cnt++;
            loop--;
            pthread_mutex_unlock(&g_mutex);
        }
    }

    return NULL;
}

int main(int argc, char **argv)
{
    pthread_t t1, t2, t3;

    pthread_mutex_init(&g_mutex, NULL);

    pthread_create(&t1, NULL, func, (void *)0);
    pthread_create(&t2, NULL, func, (void *)1);
    pthread_create(&t3, NULL, func, (void *)2);

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

    return 0;
}
读写锁

 读写锁和互斥体类似,不过读写锁有更高的并行性,互斥体要么是锁住状态,要么是不加锁状态,而且一次只有一个线程可以对其加锁。而读写锁可以有3个状态,读模式下锁住状态,写模式下锁住状态,不加锁状态。一次只有一个线程可以占有写模式的读写锁,但是多个线程可以同时占用读模式的读写锁。读写锁适合对数据结构读的次数远大于写的情况。

  当读写锁是写加锁状态时,在这个锁被解锁之前,所有试图对这个锁加锁的线程都会被阻塞。当读写锁是读加锁状态时,所有试图以读模式对它进行加锁的线程都可以得到访问权,但是任何希望以写模式对此锁进行加锁的线程都会阻塞,直到所有的线程释放它们的读锁为止。

#include<pthread.h>
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockaddr_t *restrict attr);
int pthread_rwlock_destroy(pthread_rwlock_t *restrict rwlock);
    // 成功返回0,否则返回错误码

 通过pthread_rwlock_init初始化读写锁,如果希望读写锁有默认属性,可以传一个NULL指针给attr。当不再需要读写锁时,调用pthread_rwlock_destroy做清理工作。

#include<pthread.h>
int pthread_rwlock_rdlock(pthread_rwlock_t *restrict rwlock);
int pthread_rwlock_wrlock(pthread_rwlock_t *restrict rwlock);
int pthread_rwlock_unlock(pthread_rwlock_t *restrict rwlock);
    // 成功返回0,否则返回错误码

  读写锁的读加锁、写加锁和解锁操作。

读写锁程序示例:

/**
 * 两个读线程读取数据,一个写线程更新数据
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>

#define READ_THREAD  0
#define WRITE_THREAD 1

int g_data = 0;
pthread_rwlock_t g_rwlock;

void *func(void *pdata)
{
    int data = (int)pdata;

    while (1) {
        if (READ_THREAD == data) {
            pthread_rwlock_rdlock(&g_rwlock);
            printf("-----%d------ %d\n", pthread_self(), g_data);
            sleep(1);
            pthread_rwlock_unlock(&g_rwlock);
            sleep(1);
        }
        else {
            pthread_rwlock_wrlock(&g_rwlock);
            g_data++;
            printf("add the g_data\n");
            pthread_rwlock_unlock(&g_rwlock);
            sleep(1);
        }
    }

    return NULL;
}

int main(int argc, char **argv)
{
    pthread_t t1, t2, t3;

    pthread_rwlock_init(&g_rwlock, NULL);

    pthread_create(&t1, NULL, func, (void *)READ_THREAD);
    pthread_create(&t2, NULL, func, (void *)READ_THREAD);
    pthread_create(&t3, NULL, func, (void *)WRITE_THREAD);

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

    pthread_rwlock_destroy(&g_rwlock);

    return 0;
}

##### 条件变量

条件变量是线程可用的一种同步机制,条件变量给多个线程提供了一个回合的场所,条件变量和互斥量一起使用,允许线程以无竞争的方式等待特定的条件发生。条件变量本事是由互斥体保护的,线程在改变条件状态之前必须首先锁住互斥量,其他线程在获取互斥量之前就不会觉察到这种变化,因为互斥量必须锁定之后才改变条件。

#include<pthread.h>
pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
pthread_cond_destroy(pthread_cont_t *cond);
    // 成功返回0,否则返回错误码

 使用条件变量前调用pthread_cond_init初始化,使用完毕后调用pthread_cond_destroy做清理工作。除非需要创建一个具有非默认属性的条件变量,否则pthread_cond_init函数的attr参数可以设置为NULL。

#include<pthread.h>
int pthread_cond_broadcast(pthread_cond_t *cond);
int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
    // 成功返回0,否则返回错误码

  传递给pthread_cond_wait的互斥量对条件进行保护,调用者把锁住互斥量传给函数,函数然后自动把调用线程放到等待条件的线程列表上,对互斥量解锁。这就关闭了条件检查和线程进入休眠状态等待条件改变这两个操作之间的时间通道,这样线程就不会错过条件的任何变化。pthread_cond_wait函数返回时,互斥量再次被锁住。

  pthread_cond_broadcast用广播的形式唤醒所有等待条件变量的线程。pthread_cond_signal用于唤醒一个等待条件变量的线程,至于哪个线程被唤醒,取决于线程的优先级和调度机制。有时候需要唤醒一个指定的线程,但pthread没有对该需要提供解决方法。可以间接实现该需求:定义一个能够唯一表示目标线程的全局变量,在唤醒等待条件变量的线程前先设置该变量为目标线程,然后以广播形式唤醒所有等待条件变量的线程,这些线程被唤醒后都检查改变量是否是自己,如果是就开始执行后续代码,否则继续等待。

自旋锁

  自旋锁和互斥量类似,但它不是通过休眠使进程阻塞,而是在获取锁之前一直处于忙等(自旋)状态,自旋锁可用于下面的情况:锁被持有的时间短,并且线程不希望再重新调度上花费太多的成本。自旋锁通常作为底层原语用于实现其他类型的锁。根据他们所基于的系统架构,可以通过使用测试并设置指令有效地实现。当然这里说的有效也还是会导致CPU资源的浪费:当线程自旋锁变为可用时,CPU不能做其他任何事情,这也是自旋锁只能够被只有一小段时间的原因。

#include <pthread.h>
int pthread_spin_init(pthread_spinlock_t *lock, int pshared);
int pthread_spin_destroy(pthread_spinlock_t *lock);

  pshared参数表示进程共享属性,表明自旋锁是如何获取的,如果它设为PTHREAD_PROCESS_SHARED,则自旋锁能被可以访问锁底层内存的线程所获取,即使那些线程属于不同的进程。否则pshared参数设为PTHREAD_PROCESS_PROVATE,自旋锁就只能被初始化该锁的进程内部的线程访问到。

#include <pthread.h>
int pthread_spin_lock(pthread_spinlock_t *lock);
int pthread_spin_trylock(pthread_spinlock_t *lock);
int pthread_spin_unlock(pthread_spinlock_t *lock);

  如果自旋锁当前在解锁状态,pthread_spin_lock函数不要自旋就可以对它加锁,试图对没有加锁的自旋锁进行解锁,结果是未定义的。需要注意,不要在持有自旋锁情况下可能会进入休眠状态的函数,如果调用了这些函数,会浪费CPU资源,其他线程需要获取自旋锁需要等待的时间更长了。

##### 屏障

屏障是用户协调多个线程并行工作的同步机制,屏障允许每个线程等待,直到所有合作的线程都到达某一点,然后从该点出继续执行。pthread_join其实就是一种屏障,允许一个线程等待,直到另一个线程退出。但是屏障对象的概念更广,它们允许任意数量的线程等待,直到所有的线程完成处理工作,而线程不需要退出,所有线程达到屏障后可以继续工作。

#include <pthread.h>
int pthread_barrier_init(pthread_barrier_t *restrict barrier, const pthread_barrierattr_t *restrict attr, unsigned int count);
int pthread_barrier_destroy(pthread_barrier_t *barrier);
    // 成功返回0,否则返回错误编号

 初始化屏障时,可以使用count参数指定,在允许所有线程继续运行前,必须达到屏障的线程数目。attr指定屏障属性,NULL为默认属性。

#include <pthread.h>
int pthread_barrier_wait(pthread_barrier_t *barrier);
    // 成功返回0,否则返回错误编号

  可以使用pthread_barrier_wait函数来表明,线程已完成工作,准备等所有其他线程赶过来。调用pthread_barrier_wait的线程在屏障计数未满足条件时,会进入休眠状态。如果该线程是最后一个调用pthread_barrier_wait的线程,则所有的线程会被唤醒。

  一旦到达屏障计数值,而且线程处于非阻塞状态,屏障就可以被重复使用。

#### 共享内存

https://www.cnblogs.com/liunianshiwei/p/6110384.html

system V共享内存api

结构定义:

struct shmid_ds {
               struct ipc_perm shm_perm;    /* Ownership and permissions */
               size_t          shm_segsz;   /* Size of segment (bytes) */
               time_t          shm_atime;   /* Last attach time */
               time_t          shm_dtime;   /* Last detach time */
               time_t          shm_ctime;   /* Last change time */
               pid_t           shm_cpid;    /* PID of creator */
               pid_t           shm_lpid;    /* PID of last shmat(2)/shmdt(2) */
               shmatt_t        shm_nattch;  /* No. of current attaches */
               ...
           };

共享内存函数

#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
void *shmat(int shmid, const void *shmaddr, int shmflg);
int shmdt(const void *shmaddr);
int shmctl(int shmid, int cmd, struct shmid_ds *buf);

shmget函数

功能:用来创建共享内存
原型:int shmget(key_t key, size_t size, int shmflg);
参数:
key:这个共享内存段名字
size:共享内存大小
shmflg:由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的
返回值:成功返回一个非负整数,即该共享内存段的标识码;失败返回-1

shmat函数
功能:将共享内存段连接到进程地址空间
原型:void *shmat(int shmid, const void *shmaddr, int shmflg);
参数:
shmid: 共享内存标识
shmaddr:指定连接的地址
shmflg:它的两个可能取值是SHM_RND和SHM_RDONLY
返回值:成功返回一个指针,指向共享内存第一个节;失败返回-1
shmaddr为NULL,核心自动选择一个地址
shmaddr不为NULL且shmflg无SHM_RND标记,则以shmaddr为连接地址。
shmaddr不为NULL且shmflg设置了SHM_RND标记,则连接的地址会自动向下调整为SHMLBA的整数倍。公式:shmaddr – (shmaddr % SHMLBA)
shmflg=SHM_RDONLY,表示连接操作用来只读共享内存

shmdt函数
功能:将共享内存段与当前进程脱离
原型:int shmdt(const void *shmaddr);
参数:
shmaddr: 由shmat所返回的指针
返回值:成功返回0;失败返回-1
注意:将共享内存段与当前进程脱离不等于删除共享内存段
shmctl函数
功能:用于控制共享内存
原型:intshmctl(intshmid, intcmd, structshmid_ds *buf);
参数:
shmid:由shmget返回的共享内存标识码
cmd:将要采取的动作(有三个可取值)
buf:指向一个保存着共享内存的模式状态和访问权限的数据结构
返回值:成功返回0;失败返回-1

共享内存同步问题

共享内存牵扯到同步的问题,一般有三种方案可以实现共享资源的同步

信号量,记录锁和互斥量。

       使用信号量,首先服务端创建一个只含一个信号的信号量集合,并初始化为1。占据资源,则以sem_op=-1调用semop函数。释放资源,则则以sem_op=1调用semop函数。

       使用记录锁,需要创建一个文件,并写入一个字节。分配资源,对文件获得写锁,释放资源,解锁。

​ 使用互斥量,需要所有的进程将相同的文件映射到他们的地址空间,并使用PTHREAD_PROCESS_SHARED互斥量属性在文件中初始化互斥量。分配资源,对互斥量加锁,释放资源,解锁互斥量。

##### 信号量

信号量

https://www.cnblogs.com/c-slmax/p/5277173.html

信号量可以看成一个结构体,里面包含两个字段同数据.

1.可以使用的资源数据.当资源数用完,没有资源释放,再次访问资源内核将阻塞进程

2.被阻塞的进程队列.

struct semaphore
{
  int value;
  pointer_PCB queue;
}

信号量值含义
S>0:S表示可用资源的个数
S=0:表示无可用资源,无等待进程
S<0:|S|表示等待队列中进程个数

P原语

P(s)
{
  s.value = s.value--;
  if (s.value< 0)
  {
    该进程状态置为等待状状态
    将该进程的PCB插入相应的等待队列s.queue末尾
  }
}

V原语

V(s)
{
  s.value = s.value++;
  if (s.value< =0)
  {
    唤醒相应等待队列s.queue中等待的一个进程
    改变其状态为就绪态
    并将其插入就绪队列
  }
}

semget函数
功能:用来创建和访问一个信号量集
原型
int semget(key_t key, int nsems, int semflg);
参数
key: 信号集的名字
nsems:信号集中信号量的个数
semflg: 由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的
返回值:成功返回一个非负整数,即该信号集的标识码;失败返回-1

shmctl函数
功能:用于控制信号量集
原型
int semctl(int semid, int semnum, int cmd, ...);
参数
semid:由semget返回的信号集标识码
semnum:信号集中信号量的序号
cmd:将要采取的动作(有三个可取值)
最后一个参数根据命令不同而不同
返回值:成功返回0;失败返回-1

semop函数
功能:用来创建和访问一个信号量集
原型
int semop(int semid, struct sembuf *sops, unsigned nsops);
参数
semid:是该信号量的标识码,也就是semget函数的返回值
sops:是个指向一个结构数值的指针
nsops:信号量的个数
返回值:成功返回0;失败返回-1

semop函数续
sembuf结构体:

struct  sembuf {
  short sem_num;
  short sem_op;
  short sem_flg;
};

sem_num是信号量的编号。
sem_op是信号量一次PV操作时加减的数值,一般只会用到两个值,一个是“-1”,也就是P操作,等待信号量变得可用;另一个是“+1”,也就是我们的V操作,发出信号量已经变得可用
sem_flag的两个取值是IPC_NOWAIT或SEM_UNDO,一般取0

##### 记录锁

https://www.cnblogs.com/dyllove98/archive/2013/06/28/3161371.html

记录锁相当于线程同步中读写锁的一种扩展类型,可以用来对有亲缘或无亲缘关系的进程进行文件读与写的同步,通过fcntl函数来执行上锁操作。尽管读写锁也可以通过在共享内存区来进行进程的同步,但是fcntl记录上锁往往更容易使用,且效率更高。

记录锁的功能:当一个进程正在读或修改文件的某个部分是,它可以阻止其他进程修改同一文件区。对于这个功能阐述我认为有三点要解释的:

  • 记录锁不仅仅可以用来同步不同进程对同一文件的操作,还可以通过对同一文件加记录锁,来同步不同进程对某一共享资源的访问,如共享内存,I/O设备。
  • 对于劝告性上锁,当一个进程通过上锁对文件进行操作时,它不能阻止另一个非协作进程对该文件的修改。
  • 即使是强制性上锁,也不能完全保证该文件不会被另一个进程修改。因为强制性锁对unlink函数没有影响,所以一个进程可以先删除该文件,然后再将修改后的内容保存为同一文件来实现修改。具体可参考《APUE》P367

前面我们说了记录锁相当于读写锁的一种扩展类型,记录锁和读写锁一样也有两种锁:共享读锁(F_RDLCK)和独占写锁(F_WRLCK)。在使用规则上和读写锁也基本一样:

  • 文件给定字节区间,多个进程可以有一把共享读锁,即允许多个进程以读模式访问该字节区;
  • 文件给定字节区间,只能有一个进程有一把独占写锁,即只允许有一个进程已写模式访问该字节区;
  • 文件给定字节区间,如果有一把或多把读锁,不能在该字节区再加写锁,同样,如果有一把写锁,不能再该字节区再加任何读写锁。

##### 互斥量

https://blog.csdn.net/k346k346/article/details/44652597

  在共享内存中申明互斥量pthread_mutex_t mutex,需要包含<pthread.h>头文件。使用时的步骤是:

    第一步:初始化互斥量,使用pthread_mutex_init()函数,函数原型如下:

    intpthread_mutex_init(pthread_mutex_t*restrict mutex,constpthread_mutexattr_t *restrict attr);

    使用方法:pthread_mutex_init(&mutex,NULL)。函数成功返回0,失败返回非0。

    第二步:对互斥量进程上锁,使用pthread_mutex_lock()函数,函数原型如下:

    int pthread_mutex_lock(pthread_mutex_t *mutex);

    函数成功返回0,错误返回非0的错误代码。

    第三步:pthread_mutex_unlock()对已经上锁的mutex进行解锁,pthread_mutex_lock()成对出现,函数原型如下:

    int pthread_mutex_unlock(pthread_mutex_t*mutex)。

    函数成功返回0,错误返回非0的错误代码。

    第四步:如果最后不需要使用互斥量的话,使用pthread_mutex_destroy()销毁。函数原型如下:

    int pthread_mutex_destroy(pthread_mutex_t*mutex);

点赞