【linux】多任务互斥与同步
一、同步互斥的概念互斥同一时间只能一个任务进程或线程执行谁先运行不确定。同步同一时间只能一个任务进程或线程执行有顺序的运行。二、互斥锁用于线程的互斥。互斥锁是一种简单的加锁的方法来控制对共享资源的访问。互斥锁只有两种状态,即加锁( lock )和解锁( unlock )互斥锁的操作流程如下在访问共享资源临界区域前对互斥锁进行加锁。在访问完成后释放互斥锁上的锁。 解锁对互斥锁进行加锁后任何其他试图再次对互斥锁加锁的线程将会被阻塞直到锁 被释放。互斥锁的数据类型是 pthread_mutex_t三、互斥锁API1、Pthread_mutex_init 函数#includepthread.hint pthread_mutex_init(pthread_mutex_t *mutex, 3 const pthread_mutexattr_t *attr);功能初始化一个互斥锁。参数mutex互斥锁地址。类型是 pthread_mutex_t 。attr设置互斥量的属性通常可采用默认属性即可将 attr 设为 NULL。返回值 成功0成功申请的锁默认是打开的。 失败非 0 错误码也可以使用宏 PTHREAD_MUTEX_INITIALIZER 静态初始化互斥锁比如 pthread_mutex_t mutex PTHREAD_MUTEX_INITIALIZER;这种方法等价于使用 NULL 指定的 attr 参数调用 pthread_mutex_init() 来完成动态初始 化不同之处在于 PTHREAD_MUTEX_INITIALIZER 宏不进行错误检查。2、销毁互斥锁#includepthread.hint pthread_mutex_destroy(pthread_mutex_t *mutex);功能 销毁指定的一个互斥锁。互斥锁在使用完毕后必须要对互斥锁进行销毁以释放资 源。参数 mutex互斥锁地址。返回值: 成功0 失败非 0 错误码3、申请上锁#includepthread.hint pthread_mutex_lock(pthread_mutex_t *mutex);功能 对互斥锁上锁若互斥锁已经上锁则调用者阻塞直到互斥锁解锁后再上锁。参数 mutex互斥锁地址。返回值 成功0 失败非 0 错误码尝试上锁int pthread_mutex_trylock(pthread_mutex_t *mutex);调用该函数时若互斥锁未加锁则上锁返回 0若互斥锁已加锁则函数直接返回失败即 EBUSY。4、解锁#includepthread.hint pthread_mutex_unlock(pthread_mutex_t *mutex);功能 对指定的互斥锁解锁。参数 mutex互斥锁地址。返回值 成功0 失败非0错误码例1没有互斥锁 多任务的运行情况#include stdio.h #include pthread.h #include unistd.h void *deal_fun01(void *arg) { char *str (char *)arg; int i 0; while (str[i] ! \0) { printf(%c, str[i]); fflush(stdout); //强制刷新 sleep(1); } return NULL; } void *deal_fun02(void *arg) { char *str (char *)arg; int i 0; while (str[i] ! \0) { printf(%c, str[i]); fflush(stdout); //强制刷新 sleep(1); } return NULL; } int main(int argc, char const *argv[]) { //创建两个线程 pthread_t tid1, tid2; pthread_create(tid1, NULL, deal_fun01, hello); pthread_create(tid2, NULL, deal_fun02, world); pthread_join(tid1, NULL); pthread_join(tid2, NULL); return 0; }例2有互斥锁 多任务的运行情况#include stdio.h #include pthread.h #include unistd.h pthread_mutex_t mutex; void *deal_fun01(void *arg); void *deal_fun02(void *arg); int main(int argc, char const *argv[]) { pthread_mutex_init(mutex, NULL); pthread_t tid1, tid2; pthread_create(tid1, NULL, deal_fun01, hello); pthread_create(tid2, NULL, deal_fun02, world); pthread_join(tid1, NULL); pthread_join(tid2, NULL); pthread_mutex_destroy(mutex); return 0; } void *deal_fun01(void *arg) { char *str (char *)arg; int i 0; pthread_mutex_lock(mutex); while (str[i] ! \0) { printf(%c, str[i]); fflush(stdout); sleep(1); } pthread_mutex_unlock(mutex); return NULL; } void *deal_fun02(void *arg) { char *str (char *)arg; int i 0; pthread_mutex_lock(mutex); while (str[i] ! \0) { printf(%c, str[i]); fflush(stdout); sleep(1); } pthread_mutex_unlock(mutex); return NULL; }如果是互斥 不管有多少个任务只需要一把锁所有的任务上锁 访问资源 解锁。四、死锁五、读写锁POSIX 定义的读写锁的数据类型是 pthread_rwlock_t如果某线程申请了读锁其它线程可以再申请读锁但不能申请写锁。如果某线程申请了写锁其它线程不能申请读锁,也不能申请写锁。1、初始化读写锁#includepthread.hint pthread_rwlock_init(pthread_rwlock_t *rwlock, const pthread_rwlockattr_t *attr);功能 用来初始化 rwlock 所指向的读写锁。参数 rwlock指向要初始化的读写锁指针。 attr读写锁的属性指针。返回值 成功0读写锁的状态将成为已初始化和已解锁。 失败非 0 错误码。如果 attr 为 NULL 则会使用默认的属性初始化读写锁否则 使用指定的 attr 初始化读写锁。可以使用宏 PTHREAD_RWLOCK_INITIALIZER 静态初始化读写锁比如 pthread_rwlock_t my_rwlock PTHREAD_RWLOCK_INITIALIZER;这种方法等价于使用 NULL 指定的 attr 参数调用 pthread_rwlock_init() 来完成动态初 始化不同之处在于PTHREAD_RWLOCK_INITIALIZER 宏不进行错误检查。2、销毁读写锁#includepthread.hint pthread_rwlock_destroy(pthread_rwlock_t *rwlock);功能 用于销毁一个读写锁并释放所有相关联的资源所谓的所有指的是pthread_rwlock_init() 自动申请的资源 。参数 rwlock读写锁指针。返回值 成功0 失败非 0 错误码3、申请读锁#includepthread.hint pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);功能 以阻塞方式在读写锁上获取读锁读锁定。参数 rwlock读写锁指针。 返回值 成功0 失败非 0 错误码如果没有写者持有该锁并且没有写者阻塞在该锁上则调用线程会获取读锁。如果调用线程未获取读锁则它将阻塞直到它获取了该锁。一个线程可以在一个读写锁 上多次执行读锁定。线程可以成功调用 pthread_rwlock_rdlock() 函数 n 次但是之后该线程必须调用 pthread_rwlock_unlock() 函数 n 次才能解除锁定。尝试读锁int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);用于尝试以非阻塞的方式来在读写锁上获取读锁。如果有任何的写者持有该锁或有写者阻塞在该读写锁上则立即失败返回4、申请写锁#includepthread.hint pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);功能 在读写锁上获取写锁写锁定。参数 rwlock读写锁指针。 返回值 成功0 失败非 0 错误码如果没有写者持有该锁并且没有写者读者持有该锁则调用线程会获取写锁。 如果调用线程未获取写锁则它将阻塞直到它获取了该锁。尝试写锁int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);用于尝试以非阻塞的方式来在读写锁上获取写锁。 如果有任何的读者或写者持有该锁则立即失败返回5、释放读写锁#includeint pthread_rwlock_unlock(pthread_rwlock_t *rwlock);功能 无论是读锁或写锁都可以通过此函数解锁。参数 rwlock读写锁指针。 返回值 成功0 失败非 0 错误码例两个任务读 一个任务写#include stdio.h #include pthread.h #include unistd.h pthread_rwlock_t rwlock; void *read_data01(void *arg) { int *p (int *)arg; while (1) { pthread_rwlock_rdlock(rwlock); printf(任务A:num%d\n, *p); pthread_rwlock_unlock(rwlock); sleep(1); } return NULL; } void *read_data02(void *arg) { int *p (int *)arg; while (1) { pthread_rwlock_rdlock(rwlock); printf(任务B:num%d\n, *p); pthread_rwlock_unlock(rwlock); sleep(1); } return NULL; } void *write_data(void *arg) { int *p (int *)arg; while (1) { pthread_rwlock_wrlock(rwlock); (*p); pthread_rwlock_unlock(rwlock); printf(任务C:写入num%d\n, *p); sleep(2); } return NULL; } int main(int argc, char const *argv[]) { // 定义一个公共资源 int num 0; // 初始化一把锁 pthread_rwlock_init(rwlock, NULL); pthread_t tid1, tid2, tid3; pthread_create(tid1, NULL, read_data01, (void *)num); // 读 pthread_create(tid2, NULL, read_data02, (void *)num); // 读 pthread_create(tid3, NULL, write_data, (void *)num); // 写 pthread_join(tid1, NULL); pthread_join(tid2, NULL); pthread_join(tid3, NULL); pthread_rwlock_destroy(rwlock); return 0; }六、条件变量解决死锁情况3任务中的阻塞无法解锁形成死锁的问题条件变量和互斥锁同时使用。条件变量是用来等待而不是用来上锁的条件变量本身不是锁。条件变量的两个动作 条件不满, 阻塞线程 当条件满足, 通知阻塞的线程开始工作。条件变量的类型: pthread_cond_t。1、条件变量初始化#includepthread.hint pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);功能 初始化一个条件变量参数 cond指向要初始化的条件变量指针。attr条件变量属性通常为默认值传NULL即可返回值 成功0 失败非0错误号也可以使用静态初始化的方法初始化条件变量pthread_cond_t cond PTHREAD_COND_INITIALIZER;2、释放条件变量#includepthread.hint pthread_cond_destroy(pthread_cond_t *cond);功能 销毁一个条件变量参数 cond指向要初始化的条件变量指针返回值 成功0 失败非0错误号3、等待条件#includepthread.hint pthread_cond_wait(pthread_cond_t *cond, 3 pthread_mutex_t *mutex);功能阻塞等待一个条件变量。先解锁、等待条件满足、重新上锁3步为原子操作解阻塞参数 cond指向要初始化的条件变量指针 mutex互斥锁返回值 成功0 失败非0错误号限时等待int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct *abstime);功能 限时等待一个条件变量参数 cond指向要初始化的条件变量指针 mutex互斥锁 abstime绝对时间返回值 成功0 失败非0错误号4、唤醒等待在条件变量上的线程#includepthread.hint pthread_cond_signal(pthread_cond_t *cond);功能 唤醒至少一个阻塞在条件变量上的线程参数 cond指向要初始化的条件变量返回值 成功0 失败非0错误号int pthread_cond_broadcast(pthread_cond_t *cond);功能 唤醒全部阻塞在条件变量上的线程参数 cond指向要初始化的条件变量指针返回值 成功0 失败非0错误号例生产者和消费者#include stdio.h #include pthread.h #include unistd.h #include time.h #include stdlib.h pthread_mutex_t mutex; pthread_cond_t cond; int num 3; void *consum(void *arg) // 消费 { while (1) { pthread_mutex_lock(mutex); if (num 0) { printf(%s发现仓库为空 等待生产\n, (char *)arg); pthread_cond_wait(cond, mutex); } if (num 0) { --num; printf(%s购买了一个产品 仓库剩余%d个\n, (char *)arg, num); printf(%s正在使用产品\n, (char *)arg); } pthread_mutex_unlock(mutex); sleep(rand() % 5); } return NULL; } void *product(void *arg) // 生产 { while (1) { sleep(rand() % 5); pthread_mutex_lock(mutex); num; printf(%s 放入一个产品 仓库剩余%d个\n, (char *)arg, num); pthread_cond_broadcast(cond); pthread_mutex_unlock(mutex); } return NULL; } int main(int argc, char const *argv[]) { srand(time(NULL)); pthread_mutex_init(mutex, NULL); pthread_cond_init(cond, NULL); pthread_t tid1, tid2, tid3; pthread_create(tid1, NULL, consum, 消费者A); pthread_create(tid2, NULL, consum, 消费者B); pthread_create(tid3, NULL, product, 生产者A); pthread_join(tid1, NULL); pthread_join(tid2, NULL); pthread_join(tid3, NULL); pthread_mutex_destroy(mutex); pthread_cond_destroy(cond); return 0; }七、信号量1、概述信号量广泛用于进程或线程间的同步和互斥信号量本质上是一个非负的整数计数器它被 用来控制对公共资源的访问。当信号量值大于 0 时则可以访问否则将阻塞。 PV 原语是对信号量的操作一次P操作使信号量减一次V操作使信号量加。信号量数据类型为sem_t信号量用于互斥 不管多少个任务互斥 只需要一个信号量。先P任务再 V信号量用于同步有多少个任务就需要多少个信号量。执行的任务对应的信号量为1其他信号量全 部为0。 每任务先P自己任务V下一个任务的信号量2、信号量的API1初始化信号量#includesemaphore.hint sem_init(sem_t *sem, int pshared, unsigned int value)功能 创建一个信号量并初始化它的值。一个无名信号量在被使用前必须先初始化。参数sem信号量的地址pshared等于 0信号量在线程间共享常用不等于0信号量在进程间共享。value信号量的初始值返回值 成功0 失败 - 12信号量减一 P操作int sem_wait(sem_t *sem);功能: 将信号量减一,如果信号量的值为0 则阻塞,大于0可以减一参数:信号量的地址返回值:成功返回0 失败返回-1尝试对信号量减一 int sem_trywait(sem_t *sem);功能: 尝试将信号量减一,如果信号量的值为0 不阻塞,立即返回 ,大于0可以减一参数:信号量的地址返回值:成功返回0 失败返回-13信号量加一 V操作int sem_post(sem_t *sem);功能:将信号量加一参数:信号量的地址返回值:成功返回0 失败返回-14销毁信号量int sem_destroy(sem_t *sem);功能: 销毁信号量参数: 信号量的地址返回值:成功返回0 失败返回-13、 信号量用于线程的互斥#include stdio.h #include pthread.h #include semaphore.h #include unistd.h sem_t sem; void my_printf(char *str) { int i 0; while (str[i] ! \0) { printf(%c, str[i]); fflush(stdout); sleep(1); } return; } void *task_fun01(void *arg) { sem_wait(sem); my_printf((char *)arg); sem_post(sem); return NULL; } void *task_fun02(void *arg) { sem_wait(sem); my_printf((char *)arg); sem_post(sem); return NULL; } int main(int argc, char const *argv[]) { // Add a declaration statement here sem_init(sem, 0, 1); pthread_t tid1, tid2, tid3; pthread_create(tid1, NULL, task_fun01, hello); pthread_create(tid2, NULL, task_fun02, world); pthread_join(tid1, NULL); pthread_join(tid2, NULL); pthread_join(tid3, NULL); sem_destroy(sem); return 0; }4、信号量用于线程的同步#include stdio.h #include pthread.h #include semaphore.h #include unistd.h // 定义三个信号量(用于同步) sem_t sem1; sem_t sem2; sem_t sem3; void my_printf(char *str); void *task_fun01(void *arg); void *task_fun02(void *arg); void *task_fun03(void *arg); void my_printf(char *str) { int i 0; while (str[i] ! \0) { printf(%c, str[i]); fflush(stdout); sleep(1); } return; } void *task_fun01(void *arg) { sem_wait(sem1); my_printf((char *)arg); sem_post(sem2); return NULL; } void *task_fun02(void *arg) { sem_wait(sem2); my_printf((char *)arg); sem_post(sem3); return NULL; } void *task_fun03(void *arg) { sem_wait(sem3); my_printf((char *)arg); sem_post(sem1); return NULL; } int main(int argc, char const *argv[]) { // 信号量初始化为1 第二个参数0表示用于线程 sem_init(sem1, 0, 1); sem_init(sem2, 0, 0); sem_init(sem3, 0, 0); pthread_t tid1, tid2, tid3; pthread_create(tid1, NULL, task_fun01, hello); pthread_create(tid2, NULL, task_fun02, world); pthread_create(tid3, NULL, task_fun03, linux); pthread_join(tid1, NULL); pthread_join(tid2, NULL); pthread_join(tid3, NULL); // 销毁信号量 sem_destroy(sem1); sem_destroy(sem2); sem_destroy(sem3); return 0; }5、无名信号量用于血缘关系的进程间互斥使用mmap完成无名信号量的定义#include stdio.h #include semaphore.h #include unistd.h #include sys/mman.h void my_printf(char *str); int main(int argc, char const *argv[]) { // 定义一个无名信号量 // MAP_ANONYMOUS匿名映射 ‐1不需要文件描述符 sem_t *sem mmap(NULL, sizeof(sem_t), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0); // 无名信号量的初始化 第一个1表示进程 第二个1表示初始化值1 sem_init(sem, 1, 1); pid_t pid fork(); if (pid 0) { // 子进程 sem_wait(sem); my_printf(hello); sem_post(sem); } else if (pid 0) // 父进程 { sem_wait(sem); my_printf(world); sem_post(sem); } sem_destroy(sem); return 0; } void my_printf(char *str) { int i 0; while (str[i] ! \0) { printf(%c, str[i]); fflush(stdout); sleep(1); } return; }6、无名信号量用于血缘关系的进程间同步#include stdio.h #include semaphore.h #include unistd.h #include sys/mman.h void my_printf(char *str); int main(int argc, char const *argv[]) { // 定义一个无名信号量 // MAP_ANONYMOUS匿名映射 ‐1不需要文件描述符 sem_t *sem1 mmap(NULL, sizeof(sem_t), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0); sem_t *sem2 mmap(NULL, sizeof(sem_t), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0); // 无名信号量的初始化 第一个1表示进程 第二个1表示初始化值1 sem_init(sem, 1, 1); sem_init(sem2, 1, 0); pid_t pid fork(); if (pid 0) { // 子进程 sem_wait(sem1); my_printf(hello); sem_post(sem2); } else if (pid 0) // 父进程 { sem_wait(sem2); my_printf(world); sem_post(sem1); } sem_destroy(sem1); sem_destroy(sem2); return 0; } void my_printf(char *str) { int i 0; while (str[i] ! \0) { printf(%c, str[i]); fflush(stdout); sleep(1); } return; }7、有名信号量用于无血缘的进程间互斥1、创建一个有名信号量#include fcntl.h #include sys/stat.h #include semaphore.h //信号量存在 sem_t *sem_open(const char *name, int oflag); //信号量不存在 sem_t *sem_open(const char *name, int oflag, mode_t mode, unsigned int value);功能 创建一个信号量参数name:信号量的名字oflag:sem_open函数的权限标志mode:文件权限可读、可写、可执行 0777的设置value:信号量的初始值返回值 信号量的地址失败SEM_FAILED2、信号量的关闭int sem_close(sem_t *sem);功能关闭信号量参数信号量的的地址返回值成功0 失败-13、信号量文件的删除#include semaohore.hint sem_unlink(const char *name);功能删除信号量的文件参数信号量的文件名返回值成功0 失败-1taskA.c、taskB.c#include stdio.h #include semaphore.h #include unistd.h #include fcntl.h #include sys/stat.h void my_printf(char *str) { int i 0; while (str[i] ! \0) { printf(%c, str[i]); fflush(stdout); sleep(1); } return; } int main(int argc, char const *argv[]) { // 创建一个有名信号量sem_open 最后一个参数为初始值 sem_t *sem sem_open(sem, O_RDWR | O_CREAT, 0666, 1); sem_wait(sem); my_printf(hello world); sem_post(sem); sem_close(sem); sem_destroy(sem); return 0; }8、有名信号量用于无血缘的进程间同步taskA.c#include stdio.h #include semaphore.h #include unistd.h #include fcntl.h #include sys/stat.h void my_printf(char *str) { int i 0; while (str[i] ! \0) { printf(%c, str[i]); fflush(stdout); sleep(1); } return; } int main(int argc, char const *argv[]) { // 创建一个有名信号量sem_open 最后一个参数为初始值 sem_t *sem1 sem_open(sem, O_RDWR | O_CREAT, 0666, 1); sem_t *sem2 sem_open(sem, O_RDWR | O_CREAT, 0666, 0); sem_wait(sem1); my_printf(hello world); sem_post(sem2); sem_close(sem1); sem_close(sem2); sem_destroy(sem1); sem_destroy(sem2); return 0; }taskB.c#include stdio.h #include semaphore.h #include unistd.h #include fcntl.h #include sys/stat.h void my_printf(char *str) { int i 0; while (str[i] ! \0) { printf(%c, str[i]); fflush(stdout); sleep(1); } return; } int main(int argc, char const *argv[]) { // 创建一个有名信号量sem_open 最后一个参数为初始值 sem_t *sem1 sem_open(sem, O_RDWR | O_CREAT, 0666, 1); sem_t *sem2 sem_open(sem, O_RDWR | O_CREAT, 0666, 0); sem_wait(sem2); my_printf(hello world); sem_post(sem1); sem_close(sem1); sem_close(sem2); sem_destroy(sem1); sem_destroy(sem2); return 0; }