Linux 多线程

Stella981
• 阅读 455

I. 同步机制

线程间的同步机制主要包括三个:

  • 互斥锁
    以排他的方式,防止共享资源被并发访问;
    互斥锁为二元变量, 状态为0-开锁1-上锁;
    开锁必须由上锁的线程执行,不受其它线程干扰.

  • 条件变量
    满足某个特定条件时,可通过条件变量通知其它线程do-something;
    必须与互斥锁*联合使用,单独无法执行.

  • 读写锁
    针对多读者,少写者的情况设定

    • 允许多读,但此时不可写

    • 唯一写,此时不可读.

函数的头文件为:

    #include <phtread.h>     

1. 互斥锁

操作流程:

  • I. 创建互斥锁

  • II. 申请锁:若可用,立刻占用;否则,阻塞等待

  • III. do-something

  • IV. 释放锁

  • V. 销毁锁

以下是互斥锁的基本操作函数:

功能

函数

参数

返回值

说明

初始化锁

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

1. mutex: 欲建立的互斥锁
2.attr:属性,一般为NULL

成功:0
失败:非零值

 

阻塞申请锁

int pthread_mutex_lock(
pthread_mutex_t *mutex)

mutex:互斥锁

成功:0
失败:非零值

若未申请到,
阻塞等待

非阻塞申请

int pthread_mutex_trylock(
pthread_mutex_t *mutex)

mutex:互斥锁

成功:0
失败:非零值

若未申请到,
返回错误

释放锁

int pthread_mutex_unlock(
pthread_mutex_t *mutex)

mutex:互斥锁

成功:0
失败:非零值

 

销毁锁

int pthread_mutex_destroy(
pthread_mutex_t *mutex)

mutex:互斥锁

成功:0
失败:非零值

 

2. 条件变量

注意,条件变量必须与互斥锁共同使用;

以下是条件变量的基本操作函数:

功能

函数

参数

返回值

说明

初始化锁

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

1. cond: 欲建立的条件变量
2.attr:属性,一般为NULL

成功:0
失败:非零值

 

等待条件变量

int pthread_cond_wait(
pthread_cond_t *cond,
pthread_mutex_t *mutex)

1.cond:条件变量
2.mutex:互斥锁

成功:0
失败:非零值

阻塞等待
隐含释放申请到的互斥锁

限时等待条件变量

int pthread_cond_timewait(
pthread_cond_t *cond,
pthread_mutex_t *mutex,
const struct timespec *time)

3.time:等待过期的绝对时间
从1970-1-1:0:0:0起

成功:0
失败:非零值

struct timespec{long ts_sec;
long ts_nsec}

单一通知

int pthread_cond_signal(
pthread_cond_t *cond)

cond:条件变量

成功:0
失败:非零值

唤醒等待cond的第一个线程
隐含获取需要的互斥锁

广播通知

int pthread_cond_broadcast(
pthread_cond_t *cond)

cond:条件变量

成功:0
失败:非零值

唤醒所有等待cond的线程
隐含获取需要的互斥锁

销毁条件变量

int pthread_cond_destroy(
pthread_cond_t *cond)

cond:条件变量

成功:0
失败:非零值

 

3. 读写锁

读写基本原则:

  • 若当前线程读数据,则允许其他线程读数据,但不允许写

  • 若当前线程写数据,则不允许其他线程读、写数据

以下是基本的操作:

功能

函数

参数

返回值

说明

初始化锁

int pthread_rwlock_init(
pthread_rwlock_t *rwlock,
const pthread_rwlockattr_t *attr)

1. rwlock: 欲建立的读写锁
2.attr:属性,一般为NULL

成功:0
失败:非零值

 

阻塞申请读锁

int pthread_rwlock_rdlock(
pthread_rwlock_t *rwlock)

rwlock:读写锁

成功:0
失败:非零值

若未申请到,
阻塞等待

非阻塞申请

int pthread_rwlock_tryrdlock(
pthread_rwlock_t *rwlock)

rwlock:读写锁

成功:0
失败:非零值

若未申请到,
返回错误

阻塞申请写锁

int pthread_rwlock_wrlock(
pthread_rwlock_t *rwlock)

rwlock:读写锁

成功:0
失败:非零值

若未申请到,
阻塞等待

非阻塞申请写锁

int pthread_rwlock_trywrlock(
pthread_rwlock_t *rwlock)

rwlock:读写锁

成功:0
失败:非零值

若未申请到,
返回错误

释放锁

int pthread_mutex_unlock(
pthread_rwlock_t *rwlock)

rwlock:读写锁

成功:0
失败:非零值

 

销毁锁

int pthread_rwlock_destroy(
pthread_rwlock_t *rwlock)

rwlock:读写锁

成功:0
失败:非零值

 

4. 线程信号量

线程信号量类似进程的信号量,主要是使得多个线程访问共享资源时,顺序互斥访问。
与互斥锁的区别在于:

  • 互斥锁:只有一个bool类型的值,只允许2个线程进行排队;
  • 信号量:允许多个线程共同等待一个共享资源

函数如下:

     #include <semaphore.h>

功能

函数

参数

返回值

说明

创建信号量

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

1. sem:信号量地址;
2. pshared:是(!=0)否(0)为共享信号量
3. value:信号量初值

0: 成功
-1: 失败

 

P操作(阻塞)

int sem_wait(sem_t *sem)

sem:信号量地址

0: 成功
-1: 失败

 

P操作(非阻塞)

int sem_trywait(sem_t *sem)

sem:信号量地址

0: 成功
-1: 失败

 

P操作(时间)

int sem_timedwait(sem_t *sem,
const struct timespec *abs_timeout)

1. sem:信号量地址
2. abs_timeout:超时时间

0: 成功
-1: 失败

struct timespec 见下面

V操作

int sem_post(sem_t *sem)

sem:信号量地址

0: 成功
-1: 失败

 

获取信号量值

int sem_getvalue(sem_t *sem, int *sval)

1. sem:信号量地址
2. sval: 将信号量值放到该地址

0: 成功
-1: 失败

 

删除信号量

int sem_destroy(sem_t *sem)

sem:信号量地址

0: 成功
-1: 失败

 

struct timespec {
               time_t tv_sec;      /* Seconds */
               long   tv_nsec;     /* Nanoseconds [0 .. 999999999] */ };

II. 异步机制 - 信号

线程的异步机制只有信号,类似于线程的信号。

线程信号具备以下特点

    1. 任何线程都可以向其它线程(同一进程下)发送信号;
    1. 每个线程都具备自己独立的信号屏蔽集,不影响其它线程;
    1. 线程创建时,不继承原线程的信号屏蔽集;
    1. 同进程下,所有线程共享对某信号的处理方式,即一个设置,所有有效;
    1. 多个线程的程序,向某一个线程发送终止信号,则整个进程终止

信号的基本操作如下:

功能

函数

参数

返回值

说明

安装信号

sighandler_t signal(
int signum,
sighandler_t handler)

1.signum:信号值
2.handler:信号操作

详情参见:
http://www.cnblogs.com/Jimmy1988/p/7575103.html

发送信号

int pthread_kill(
pthread_t threadid,
int signo

1.threadid: 目标线程id
2.signo:信号值

成功:0
失败:非零值

若signo=0,
检测该线程是否存在,
不发送信号

设置屏蔽集

pthread_sigmask(int how,
const sigset_t *set,
sigset_t *oldset)

1.how:如何更改信号掩码
2.newmask:新的信号屏蔽集
3.原信号屏蔽集

成功:0
失败:非零值

how值:
 1.SIG_BLOCK:添加新掩码
 2.SIG_UNBLOCK:删除新掩码
 3.SIG_SETMASK:设置新掩码完全替换旧值

也可以参考这篇博客:https://www.cnblogs.com/coding-my-life/p/4782529.html

III、示例代码

1.同步机制:

1). 互斥锁:

两个线程:

  • 读线程:从stdin中读取数据,并存储
  • 写线程:从存储buffer中读取数据并显示
#include <stdio.h>
#include <string.h> #include <pthread.h> #include <unistd.h> #include <stdlib.h> #define SIZE 128 pthread_mutex_t mutex; int EXIT = 0; char word[SIZE]; void * child(void *arg) { while(1) { while(strlen(word) == 0) usleep(100); pthread_mutex_lock(&mutex); printf("The input words: %s\n", word); pthread_mutex_unlock(&mutex); if(strcmp("end\n", word) == 0) { printf("The process end\n"); EXIT = 1; break; } memset(word, '\0', SIZE); } return ; } int main() { //1. create the lock pthread_mutex_init(&mutex, NULL); //2.create a new thread pthread_t tid; pthread_create(&tid, NULL, (void *)*child, NULL); //3. Input words while(EXIT == 0) { if(strlen(word)!=0) usleep(100); //add the lock else { pthread_mutex_lock(&mutex); printf("Input words: "); fgets(word, SIZE, stdin); pthread_mutex_unlock(&mutex); } } pthread_join(tid, NULL); printf("The child has joined\n"); pthread_mutex_destroy(&mutex); return 0; } 

2). 条件变量:

生产者和消费者问题:

  • 生产者:
    向仓库生产数据(大小可任意设定),当满时,阻塞等待仓库有空闲(由消费者消费完后通知)

  • 消费者:
    从仓库读数据,若仓库为空,则阻塞等待,当生产者再次生产产品后通知

#include <stdio.h>
#include <string.h> #include <stdlib.h> #include <unistd.h> #include <pthread.h> #define SIZE 2 int Data[SIZE]; typedef struct { pthread_mutex_t lock; pthread_cond_t notFull; pthread_cond_t notEmpty; int read_point; int write_point; }sCOND; sCOND *pCondLock; void init(void) { //memset(pCondLock, 0, sizeof(sCOND)); //1.Create a mutex lock pthread_mutex_init(&pCondLock->lock, NULL); //2.Create two condition variable pthread_cond_init(&pCondLock->notFull, NULL); pthread_cond_init(&pCondLock->notEmpty, NULL); //set the read and write point 0 pCondLock->read_point = 0; pCondLock->write_point = 0; } int put(int data) { //obtain the mutex lock pthread_mutex_lock(&pCondLock->lock); //check the global variable Data full or not while((pCondLock->write_point+1)%SIZE == pCondLock->read_point) { printf("The buf is full, waitting for not_full signal\n"); pthread_cond_wait(&pCondLock->notFull, &pCondLock->lock); } //write the data to buffer Data[pCondLock->write_point] = data; pCondLock->write_point++; if(pCondLock->write_point == SIZE) pCondLock->write_point = 0; //unlock the mutex lock pthread_mutex_unlock(&pCondLock->lock); //wake up the not_empty signal pthread_cond_signal(&pCondLock->notEmpty); return 0; } int get(int *data) { //obtain the mutex lock pthread_mutex_lock(&pCondLock->lock); //check the global variable Data empty or not while(pCondLock->write_point == pCondLock->read_point) { printf("The buf is empty, waitting for not_empty signal\n"); pthread_cond_wait(&pCondLock->notEmpty, &pCondLock->lock); } //read the data from buffer *data = Data[pCondLock->read_point]; pCondLock->read_point++; if(pCondLock->read_point == SIZE) pCondLock->read_point = 0; //wake up the not_empty signal pthread_cond_signal(&pCondLock->notFull); pthread_mutex_unlock(&pCondLock->lock); return *data; } void *produce(void) { int times=0; //1. first 5 times, every second write a data to buffer for(times=0; times < 5; times++) { sleep(1); put(times+1); printf("Input date=%d\n", times+1); } //2. last 5 times, every 3 seconds write a data to buffer for(times = 5; times < 10; times++) { sleep(3); put(times+1); printf("Input date=%d\n", times+1); } } void *consume(void) { int times=0; int data=0; //10 times, every 2 seconds read the buffer for(times = 0; times < 10; times++) { sleep(2); data = get(&data); printf("The data is %d\n", data); } } int main() { pthread_t tid1, tid2; pCondLock = malloc(sizeof(sCOND)); memset(pCondLock, '\0', sizeof(sCOND)); //1.init the struct of sCondLock init(); //2. start two threads pthread_create(&tid1, NULL, (void*)*produce, NULL); pthread_create(&tid2, NULL, (void*)*consume, NULL); pthread_join(tid1, NULL); pthread_join(tid2, NULL); free(pCondLock); return 0; } 

3). 读写锁:

四个线程:两读两写;

多进程可同时读,但此时不可写;
只有一个线程可写,其它线程等待该线程写完后执行响应的读/写操作

#include <stdio.h>
#include <unistd.h> #include <string.h> #include <pthread.h> #include <stdlib.h> #define BUF_SIZE 128 char buf[BUF_SIZE]; pthread_rwlock_t rwlock; int time_to_exit = 0; void *read_first(void *arg); void *read_second(void *arg); void *write_first(void *arg); void *write_second(void *arg); int main() { pthread_t tid_rd1, tid_rd2; pthread_t tid_wr1, tid_wr2; //1.create a read-write-lock int ret = pthread_rwlock_init(&rwlock, NULL); if(ret != 0) { perror("pthread_rwlock_init"); exit(EXIT_FAILURE); } //2. Create the read and write threads ret = pthread_create(&tid_rd1, NULL, (void *)*read_first, NULL); if(ret != 0) { perror("pthread_create"); exit(EXIT_FAILURE); } ret = pthread_create(&tid_rd2, NULL, (void *)*read_second, NULL); if(ret != 0) { perror("pthread_create"); exit(EXIT_FAILURE); } ret = pthread_create(&tid_wr1, NULL, (void *)*write_first, NULL); if(ret != 0) { perror("pthread_create"); exit(EXIT_FAILURE); } ret = pthread_create(&tid_wr2, NULL, (void *)*write_second, NULL); if(ret != 0) { perror("pthread_create"); exit(EXIT_FAILURE); } //3. wait for the threads finish pthread_join(tid_rd1, NULL); pthread_join(tid_rd2, NULL); pthread_join(tid_wr1, NULL); pthread_join(tid_wr2, NULL); //4. delete the read-write-lock pthread_rwlock_destroy(&rwlock); return 0; } /***************************************************/ // Write threads void *write_first(void *arg) { while(!time_to_exit) { sleep(5); //1. get the read-lock pthread_rwlock_wrlock(&rwlock); printf("\nThis is thread write_first!\n"); printf("Pls input the string: "); fgets(buf, BUF_SIZE, stdin); pthread_rwlock_unlock(&rwlock); } printf("Exit the write_first!\n"); pthread_exit(0); } void *write_second(void *arg) { while(!time_to_exit) { sleep(10); //1. get the read-lock pthread_rwlock_wrlock(&rwlock); printf("\nThis is thread write_second!\n"); printf("Pls input the string: "); fgets(buf, BUF_SIZE, stdin); pthread_rwlock_unlock(&rwlock); } printf("Exit the write_second!\n"); pthread_exit(0); } //-----2. read the threads void *read_first(void *arg) { while(1) { sleep(5); pthread_rwlock_rdlock(&rwlock); printf("\nThis is thread read_first\n"); //if write an string of "end" if(!strncmp("end", buf, 3)) { printf("Exit the read_first!\n"); break; } //if nothing in the BUFFER while(strlen(buf) == 0) { pthread_rwlock_unlock(&rwlock); sleep(2); pthread_rwlock_rdlock(&rwlock); } //output the string in BUFFER printf("The string is: %s\n", buf); pthread_rwlock_unlock(&rwlock); } pthread_rwlock_unlock(&rwlock); //make the exit true time_to_exit = 1; pthread_exit(0); } void *read_second(void *arg) { while(1) { sleep(4); pthread_rwlock_rdlock(&rwlock); printf("\nThis is thread read_second\n"); //if write an string of "end" if(!strncmp("end", buf, 3)) { printf("Exit the read_second!\n"); break; } //if nothing in the BUFFER while(strlen(buf) == 0) { pthread_rwlock_unlock(&rwlock); sleep(2); pthread_rwlock_rdlock(&rwlock); } //output the string in BUFFER printf("The string is: %s\n", buf); pthread_rwlock_unlock(&rwlock); } pthread_rwlock_unlock(&rwlock); //make the exit true time_to_exit = 1; pthread_exit(0); } 

2. 异步机制 - 信号:

本程序包括两个线程:

  • 线程1安装SIGUSR1,阻塞除SIGUSR2外的所有信号;

  • 线程2安装SIGUSR2,不阻塞任何信号

操作流程:

  • 1- 线程1、2安装信号;

  • 2- 主线程发送SIGUSR1和SIGUSR2至线程1和线程2;

  • 3- 线程1接收到除SIGUSR2之外的信号,阻塞不执行;当收到SIGUSR2后,执行对应操作;

  • 4- 线程2接收到SIGUSR1和SIGUSR2后,分别执行对应操作

  • 5- 主线程发送SIGKILL信号,结束整个进程

#include <stdio.h>
#include <string.h> #include <unistd.h> #include <pthread.h> #include <signal.h> #include <stdlib.h> void *th_first(void *arg); void *th_second(void *arg); pthread_t tid1, tid2; void handler(int signo) { printf("In handler: tid_%s, signo=%d\n", ((pthread_self() == tid1)?"first":"second"), signo); } int main() { int ret = 0; //1. create first thread ret = pthread_create(&tid1, NULL, (void *)*th_first, NULL); if(0 !=ret) { perror("pthread_create"); exit(EXIT_FAILURE); } //2. create second thread ret = pthread_create(&tid2, NULL, (void *)*th_second, NULL); if(0 !=ret) { perror("pthread_create"); exit(EXIT_FAILURE); } sleep(2); //3. send the signal of SIG_USER1 and SIG_USER2 to thread_first ret = pthread_kill(tid1, SIGUSR1); if(0 !=ret) { perror("pthread_kill"); exit(EXIT_FAILURE); } ret = pthread_kill(tid1, SIGUSR2); if(0 !=ret) { perror("pthread_kill"); exit(EXIT_FAILURE); } //4. send the signal of SIG_USER1 and SIG_USER2 to thread_second_ sleep(1); ret = pthread_kill(tid2, SIGUSR1); if(0 !=ret) { perror("pthread_kill"); exit(EXIT_FAILURE); } ret = pthread_kill(tid2, SIGUSR2); if(0 !=ret) { perror("pthread_kill"); exit(EXIT_FAILURE); } sleep(1); //5. send SIGKILL to all threads ret = pthread_kill(tid1, SIGKILL); if(0 !=ret) { perror("pthread_kill"); exit(EXIT_FAILURE); } pthread_join(tid1, NULL); pthread_join(tid2, NULL); return 0; } void *th_first(void *arg) { //1. Add SIGUSR1 signal signal(SIGUSR1, handler); //2. Set the sinagl set sigset_t set; sigfillset(&set); //init set to be full, include all signal sigdelset(&set, SIGUSR2); //delete the SIGUSR2 from the set variable pthread_sigmask(SIG_SETMASK, &set, NULL); //set the current mask set to be defined set variable //3. Circular wait the signal int i; for(i=0; i<5; i++) { printf("\nThis is th_first, tid=%#x\n ", pthread_self()); pause(); } } void *th_second(void *arg) { usleep(100); //1. Add the signal of SIGUSR2 signal(SIGUSR2, handler); //2. Circular wait the signal int i; for(i=0; i<5; i++) { printf("\nThis is th_second, tid=%#x\n", pthread_self()); pause(); } }
点赞
收藏
评论区
推荐文章
blmius blmius
2年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Stella981 Stella981
2年前
KVM调整cpu和内存
一.修改kvm虚拟机的配置1、virsheditcentos7找到“memory”和“vcpu”标签,将<namecentos7</name<uuid2220a6d1a36a4fbb8523e078b3dfe795</uuid
Wesley13 Wesley13
2年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
2年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
2年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
2年前
ES6 新增的数组的方法
给定一个数组letlist\//wu:武力zhi:智力{id:1,name:'张飞',wu:97,zhi:10},{id:2,name:'诸葛亮',wu:55,zhi:99},{id:3,name:'赵云',wu:97,zhi:66},{id:4,na
Wesley13 Wesley13
2年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
5个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这