基层网站建设作用,西安app开发,有限公司章程,搭建商城到底哪家好1. 概述
当有多个线程访问同一个共享资源#xff08;临界资源#xff09;时#xff0c;且不允许同时访问#xff0c;那么就需要线程同步。常见的线程同步方式#xff1a;互斥锁、读写锁、条件变量、信号量。
2. 互斥锁
互斥锁的方式可以简单概括为#xff1a;锁定操作… 1. 概述
当有多个线程访问同一个共享资源临界资源时且不允许同时访问那么就需要线程同步。常见的线程同步方式互斥锁、读写锁、条件变量、信号量。
2. 互斥锁
互斥锁的方式可以简单概括为锁定操作临界资源的代码片段锁定后每次只能由一个线程来进行操作。这样能够解决多个线程同时访问临界资源造成的数据混乱但是降低了执行效率因为并行操作变成了串行操作。
【1】互斥锁类型pthread_mutex_t。创建一个该类型的变量就得到一把互斥锁。该变量中保存了锁的状态打开还是锁定若为锁定则保存了加锁的线程ID。锁定时其他线程将会阻塞直到这个互斥锁被打开。 以下函数的返回值成功返回0失败返回错误号。 【2】初始化互斥锁
//restrict是一个修饰指针的关键字该关键字修饰的指针可以访问指向的内存地址其他指针不行
int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);
//mutex: 互斥锁地址
//attr: 互斥锁属性一般为NULL默认属性
【3】释放互斥锁资源
int pthread_mutex_destroy(pthread_mutex_t *mutex);
【4】加锁阻塞
int pthread_mutex_lock(pthread_mutex_t *mutex);
//若锁是打开的那么加锁成功锁会记录线程ID。
//若锁是锁定的那么加锁失败线程阻塞在此直到上一个线程解锁。【5】加锁非阻塞
int pthread_mutex_trylock(pthread_mutex_t *mutex);
//若加锁失败则不会阻塞而是直接返回错误号。
【6】解锁
int pthread_mutex_unlock(pthread_mutex_t *mutex);
//解锁还需加锁人哪个线程加的锁就得哪个线程来解锁。 程序实例创建两个子线程对全局变量number进行1操作。若不使用互斥锁就会造成数据混乱。使用了互斥锁运行正常。 #include stdio.h
#include pthread.h
#include unistd.hpthread_mutex_t mutex; //全局的互斥锁
int number 0; //全局变量//线程t1执行函数
void* funA(void *arg)
{for (int i 0; i 50; i) {pthread_mutex_lock(mutex);int cur number;cur;usleep(10);number cur;printf(线程1的ID: %ld, number: %d\n, pthread_self(), number);pthread_mutex_unlock(mutex);}return NULL;
}//线程t2执行函数
void* funB(void *arg)
{for (int i 0; i 50; i) {pthread_mutex_lock(mutex);int cur number;cur;number cur;printf(线程2的ID: %ld, number: %d\n, pthread_self(), number);usleep(5);pthread_mutex_unlock(mutex);}return NULL;
}//主函数
int main(int argc, char **argv)
{/* 初始化互斥锁 */pthread_mutex_init(mutex, NULL);/* 创建两个子线程 */pthread_t t1, t2;pthread_create(t1, NULL, funA, NULL);pthread_create(t2, NULL, funB, NULL);/* 阻塞回收两个子线程*/pthread_join(t1, NULL);pthread_join(t2, NULL);/* 销毁互斥锁 */pthread_mutex_destroy(mutex);return 0;
} 3. 死锁
互斥锁若使用不当就会造成死锁。一旦多线程造成死锁就会使得所有线程处于阻塞状态且无法解除。常见的死锁场景①加锁后忘记解锁②重复加锁③存在多个共享资源随意加锁。避免死锁①多检查代码②对共享资源访问完毕后解锁或者在加锁时使用trylock非阻塞③引入一些专门用于死锁检测的模块④如果程序中有多把锁, 可以控制对锁的访问顺序。另外在加其它锁之前先释放拥有的互斥锁。
4. 自旋锁
【1】介绍
自旋锁与互斥锁类似但是自旋锁在试图上锁时是不断“自旋”查看锁是否被释放CPU资源占用多。因此使用自旋锁的效率不高在用户程序中很少使用自旋锁更多的是在内核中使用、针对执行时间少的代码。互斥锁是基于自旋锁实现的所以自旋锁更加底层。
【2】自旋锁类型pthread_spinlock_t。创建一个该类型的变量就得到一把自旋锁。 以下函数的返回值成功返回0失败返回错误号。 【3】初始化/销毁自旋锁
int pthread_spin_init(pthread_spinlock_t *lock, int pshared);//初始化
//lock: 自旋锁的地址
//pshared: 表示自旋锁的进程共享属性//PTHREAD_PROCESS_SHARED: 共享自旋锁。该自旋锁可以在多个进程中的线程之间共享//PTHREAD_PROCESS_PRIVATE 私有自旋锁。只有本进程内的线程才能够使用该自旋锁int pthread_spin_destroy(pthread_spinlock_t *lock);//销毁
【4】自旋锁的加锁和解锁
int pthread_spin_lock(pthread_spinlock_t *lock); //加锁一直自旋
int pthread_spin_trylock(pthread_spinlock_t *lock); //加锁返回错误错误码EBUSY
int pthread_spin_unlock(pthread_spinlock_t *lock); //解锁 程序实例创建两个子线程来对全局变量1每个线程循环10000次。 #include stdio.h
#include pthread.h
#include stdlib.hint count 0;//全局变量临界区资源
pthread_spinlock_t locker;//自旋锁/* 线程执行函数 */
void* doing(void* arg)
{for (int i 0; i 10000; i) {//自旋锁加锁pthread_spin_lock(locker);//操作临界区资源int tmp count;tmp;count tmp;//自旋锁解锁pthread_spin_unlock(locker);}return NULL;
}/* 主函数 */
int main(int argc, char** argv)
{//初始化自旋锁pthread_spin_init(locker, PTHREAD_PROCESS_PRIVATE);//创建两个子线程pthread_t tid[2];for (int i 0; i 2; i) {if (pthread_create(tid[i], NULL, doing, NULL)) {perror(pthread_create error);exit(-1);}}//阻塞回收子线程for (int i 0; i 2; i) {if (pthread_join(tid[i], NULL)) {perror(pthread_join error);exit(-1);}}//打印全局变量printf(count %d\n, count);//销毁自旋锁pthread_spin_destroy(locker);return 0;
} 5. 读写锁
读写锁可以视为互斥锁的升级版可以指定锁定的是读操作还是写操作且同一时间内只能锁定其中一个操作。读写锁的使用方式与互斥锁相同。
【1】读写锁类型pthread_rwlock_t。创建一个该类型的变量就得到一把读写锁。读写锁中保存了以下信息①锁的状态打开还是锁定②锁的是哪个操作读/写③哪个线程锁定了这把锁。
【2】读写锁特点
使用读写锁的读锁锁定了临界区那么读操作是并行的。使用读写锁的写锁锁定了临界区那么写操作是串行的。使用读写锁的读锁和写锁分别锁定了两个临界区那么访问写锁的线程优先进入。因为写锁的优先级高于读锁。 以下函数的返回值成功返回0失败返回错误号。 【3】初始化读写锁
//restrict是一个修饰指针的关键字该关键字修饰的指针可以访问指向的内存地址其他指针不行
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,const pthread_rwlockattr_t *restrict attr);
//rwlock: 读写锁地址
//attr: 读写锁属性一般为NULL默认属性
【3】释放读写锁资源
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
【4】锁定读操作阻塞
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
//若读写锁均打开那么锁定读操作成功。
//若读写锁中读锁锁定那么锁定读操作成功。因为读锁是共享的。
//若读写锁中写锁锁定那么会阻塞。
【5】锁定读操作非阻塞
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
//若读写锁均打开那么锁定读操作成功。
//若读写锁中读锁锁定那么锁定读操作成功。因为读锁是共享的。
//若读写锁中写锁锁定那么会返回错误号而不会阻塞。
【6】锁定写操作阻塞
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
//若读写锁均打开锁定写操作成功。
//若读写锁中读锁或写锁锁定了那么就会阻塞。
【7】锁定写操作非阻塞
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
//若读写锁均打开锁定写操作成功。
//若读写锁中读锁或写锁锁定了那么返回错误号而不会阻塞。
【8】解锁
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
//不管锁定的是读锁还是写锁都能解锁。 程序实例创建3个子线程进行写操作创建5个子线程进行读操作。它们都针对一个全局变量。
#include stdio.h
#include pthread.h
#include unistd.h
#include stdlib.hint number 0; //全局变量临界资源
pthread_rwlock_t rwlocker; //全局的读写锁/* 写线程执行函数 */
void* writeNum(void *arg)
{while (1) {pthread_rwlock_wrlock(rwlocker);int cur number;cur;number cur;printf(写操作完毕number %d, tid: %ld\n, number, pthread_self());pthread_rwlock_unlock(rwlocker);usleep(rand() % 100);//让子线程交替写}return NULL;
}/* 读线程执行函数 */
void* readNum(void *arg)
{while (1) {pthread_rwlock_rdlock(rwlocker);printf(读操作完毕number %d, tid: %ld\n, number, pthread_self());pthread_rwlock_unlock(rwlocker);usleep(rand() % 100);}return NULL;
}/* 主函数 */
int main(int argc, char **argv)
{//初始化读写锁pthread_rwlock_init(rwlocker, NULL);//创建8个线程3个为写线程5个为读线程pthread_t wtid[3];pthread_t rtid[5];for (int i 0; i 3; i) {pthread_create(wtid[i], NULL, writeNum, NULL);}for (int i 0; i 5; i) {pthread_create(rtid[i], NULL, readNum, NULL);}//主线程回收8个线程for (int i 0; i 3; i) {pthread_join(wtid[i], NULL);}for (int i 0; i 5; i) {pthread_join(rtid[i], NULL);}//释放读写锁pthread_rwlock_destroy(rwlocker);return 0;
} 6. 条件变量
条件变量的作用是进行线程的阻塞而不是线程同步。当满足某个特定条件时才会阻塞线程。一般用于生产者-消费者模型且和互斥锁相互配合。
【1】条件变量类型pthread_cond_t。被条件变量阻塞的线程的信息会被记录到该类型的变量中以便在解除阻塞时使用。
【2】初始化条件变量
int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr);
//cond条件变量的地址
//attr条件变量的属性一般为NULL。
【3】释放条件变量资源
int pthread_cond_destroy(pthread_cond_t *cond);
【4】阻塞线程
// 线程阻塞函数, 哪个线程调用这个函数, 哪个线程就会被阻塞
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);//mutex是互斥锁用于线程同步。
//当阻塞线程时若线程已经对互斥锁mutex上锁那么会将这把锁打开这样做是为了避免死锁
//当线程解除阻塞时函数内部会帮助这个线程再次将这个mutex锁上继续向下访问临界区
【5】阻塞线程时间到解除
// 将线程阻塞一定的时间长度, 时间到达之后, 线程就解除阻塞了
int pthread_cond_timedwait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime);// 以下结构体表示的时间是从1971.1.1到某个时间点的时间, 总长度使用秒/纳秒表示
struct timespec {time_t tv_sec; /* Seconds */long tv_nsec; /* Nanoseconds [0 .. 999999999] */
};//使用示例
time_t mytim time(NULL); // 1970.1.1 0:0:0 到当前的总秒数
struct timespec tmsp;
tmsp.tv_nsec 0;
tmsp.tv_sec time(NULL) 100; // 线程阻塞100s
【6】解除阻塞至少一个线程
int pthread_cond_signal(pthread_cond_t *cond);
【7】解除阻塞全部线程
int pthread_cond_broadcast(pthread_cond_t *cond); 生产者-消费者模型 ①若干个生产者线程生产商品放入任务队列中若任务队列满则阻塞可以使用一个生产者条件变量来控制是否阻塞。 ②若干个消费者线程消费者从任务队列中拿走商品若任务队列空则阻塞可以使用一个消费者条件变量来控制是否阻塞。 ③任务队列可以是数组、链表、stl容器等等。 程序实例使用条件变量实现生产者-消费者模型生产者线程有5个往链表头部添加节点消费者线程也有5个删除链表头部节点。 #include stdio.h
#include pthread.h
#include unistd.h
#include stdlib.h/* 单链表定义 */
typedef struct LNode {int number;struct LNode *next;
} LNode;/* 全局变量及锁 */
pthread_mutex_t mutex; //互斥锁
pthread_cond_t cond; //条件变量
LNode *head NULL; //临界资源单链表/* 生产者执行函数 */
void* produce(void *arg)
{while (1) {//生产商品往链表头部添加节点pthread_mutex_lock(mutex);LNode *tmp (LNode*)malloc(sizeof(LNode));//创建新节点tmp-number rand() % 100;tmp-next head;head tmp;printf(生产完毕! 新节点number: %d, 线程ID: %ld\n, tmp-number, pthread_self());pthread_mutex_unlock(mutex);//通知消费者拿走商品pthread_cond_broadcast(cond);sleep(rand() % 3);//生产慢一点}return NULL;
}/* 消费者执行函数 */
void* consume(void *arg)
{while (1) {pthread_mutex_lock(mutex);//无商品则等待//这里不能用if可能出现段错误如下场景//假设消费者线程1进入后阻塞然后切换到生产者线程解除其阻塞//然后切换到消费者线程2拿走了商品此时链表又是空。//接着切换回消费者线程1由于if之前已经判定过了这里直接进行后续操作//从而出现段错误。因此通过while循环判断当阻塞解除后也会再次判断。while (head NULL) {pthread_cond_wait(cond, mutex);}//拿走商品删除链表头部节点LNode *tmp head;printf(消费完毕! 节点number: %d, 线程ID: %ld\n, tmp-number, pthread_self());head head-next;free(tmp);pthread_mutex_unlock(mutex);sleep(rand() % 3);}return NULL;
}/* 主函数 */
int main(int argc, char **argv)
{//初始化锁和条件变量pthread_mutex_init(mutex, NULL);pthread_cond_init(cond, NULL);//创建5个生产者线程和5个消费者线程pthread_t ptid[5];pthread_t ctid[5];for (int i 0; i 5; i) {pthread_create(ptid[i], NULL, produce, NULL);}for (int i 0; i 5; i) {pthread_create(ctid[i], NULL, consume, NULL);}//主线程回收10个线程for (int i 0; i 5; i) {pthread_join(ptid[i], NULL);}for (int i 0; i 5; i) {pthread_join(ctid[i], NULL);}//释放互斥锁和条件变量pthread_mutex_destroy(mutex);pthread_cond_destroy(cond);return 0;
} 7. 信号量
信号量信号灯用在多线程的多任务同步中一个线程完成了某个任务就通过信号量告诉其它线程其它线程再进行相关操作。信号量也是用于阻塞线程要保证线程安全需要信号量与互斥锁一起使用。
【1】信号量类型sem_t。需要添加头文件semaphore.h.
【2】初始化信号量
int sem_init(sem_t *sem, int pshared, unsigned int value);
//sem: 信号量地址
//pshared: 0表示线程同步非0表示进程同步
//value: 初始化当前信号量拥有的资源数0, 若资源数为0则阻塞
【3】释放信号量资源
int sem_destroy(sem_t *sem);
//sem: 信号量地址
【4】消耗资源函数阻塞
//sem: 信号量地址
//函数被调用sem中的资源就会被消耗1个
//当资源数为0时线程就会阻塞
int sem_wait(sem_t *sem);
【5】消耗资源函数非阻塞
//sem: 信号量地址
//函数被调用sem中的资源就会被消耗1个
//当资源数为0时线程就会返回错误号而不会阻塞
int sem_trywait(sem_t *sem);
【6】时间到解除阻塞
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
//sem: 信号量地址
//abs_timeout: 与pthread_cond_timedwait中的参数一样
【7】增加资源函数
//给sem中的资源数1
//若资源数从0加到1那么阻塞的线程就会解除阻塞
int sem_post(sem_t *sem);
【8】查看资源数
//查看信号量sem中的整形数值, 这个值会被写到sval指针对应的内存中
int sem_getvalue(sem_t *sem, int *sval); 程序实例使用信号量实现生产者-消费者模型生产者线程有5个往链表头部添加节点消费者线程也有5个删除链表头部节点。同时限定了平台容纳商品的最大容量为6个。我们可以设置两个信号量分别来代表生产者线程生产的商品数量、平台中现有的商品数量用于给消费者线程拿走。 #include stdio.h
#include pthread.h
#include semaphore.h
#include stdlib.h
#include unistd.h/* 单链表节点声明 */
typedef struct LNode{int number;struct LNode *next;
} LNode;/* 全局变量 */
sem_t psem; //生产者生产商品的信号量
sem_t csem; //消费者拿走时的信号量
pthread_mutex_t mutex; //互斥锁
LNode *head NULL; //单链表临界区资源/* 生产者线程执行函数 */
void* produce(void *arg)
{while (1) {//生产者信号量-1sem_wait(psem);//生产者生产商品pthread_mutex_lock(mutex);LNode *tmp (LNode*)malloc(sizeof(LNode));tmp-number rand() % 100;tmp-next head;head tmp;printf(生产完毕新节点number: %d, 线程ID: %ld\n, tmp-number, pthread_self());pthread_mutex_unlock(mutex);//消费者信号量1sem_post(csem);sleep(rand() % 3);}return NULL;
}/* 消费者线程执行函数 */
void* consume(void *arg)
{while (1) {//消费者信号量-1sem_wait(csem);//消费者拿走商品pthread_mutex_lock(mutex);LNode *tmp head;head head-next;printf(消费完毕节点number: %d, 线程ID: %ld\n, tmp-number, pthread_self());free(tmp);pthread_mutex_unlock(mutex);//生产者信号量1sem_post(psem);sleep(rand() % 3);}return NULL;
}/* 主函数 */
int main(int argc, char **argv)
{//初始化信号量和互斥锁sem_init(psem, 0, 6);//平台最多容纳的商品数sem_init(csem, 0, 0);//平台最初没有商品pthread_mutex_init(mutex, NULL);//创建5个生产者线程5个消费者线程pthread_t ptid[5];pthread_t ctid[5];for (int i 0; i 5; i) {pthread_create(ptid[i], NULL, produce, NULL);}for (int i 0; i 5; i) {pthread_create(ctid[i], NULL, consume, NULL);}//释放线程资源for (int i 0; i 5; i) {pthread_join(ptid[i], NULL);}for (int i 0; i 5; i) {pthread_join(ctid[i], NULL);}//释放其它资源sem_destroy(psem);sem_destroy(csem);pthread_mutex_destroy(mutex);return 0;
}