多线程、多进程总结(Linux)
Vim基本操作
(在非编辑模式下)
x(小X) ---表示向后删除一个字符,即是删除光标所在字符
X(大X) ---X表示删除光标前的一个字符,不包括光标所在字符
nx ---n表示数字,表示向后删除每个字符,例如:10x表示删除包括光标在内的后面10个字符
dd ---删除光标所在的行
ndd ---n表示数字,删除光标所在的向下n行
d1G ---删除光标所在行到第一行数据
dG ---删除光标所在行到最后一行数据
d0 ---删除光标所在到该行第一个字符
d$ ---删除光标所在到该行最后一个字符
yy ---复制光标所在的行
nyy ---n表示数字,复制光标所在的向下n行
y1G ---复制光标所在行到第一行数据
yG ---复制光标所在行到最后一行数据
y0 ---复制光标所在到该行第一个字符
y$ ---复制光标所在到该行最后一个字符
p(小p) ---表示将已复制的数据粘贴在光标的下一行
P(大P) ---表示将已复制的数据粘贴在光标的上一行
J ---将光标所在的行与下一行的数据合并为一行
u ---小表示撤销上一步操作
ctrtr+r ---相反,表示重做前一步操作
.(点) ---表示重复前一个操作,例:想多次复制,一直按这个点就行了
/xxx ---查找xxx
:set nu! ---显示行号;
Linux中C的使用
C的指针
1.指针基本运用:
#include<stdio.h>
int main()
{
int *p,m = 10;
p = &m;
printf("%d\n",m);
printf("%d\n",&m);
printf("%d\n",p);
printf("%d\n",*p);
printf("%d\n",&p);
return 0;
}
运行结果:
10 //m的值
6487572 //m的地址
6487572 //p的值(存放m的地址)
10 //指针p指向地址的值
6487576 //指针变量p本身的地址
2.指针作为参数传递:
#include<stdio.h>
void test(void *arg)
{
printf("arg存放的值:%p\n",arg);
printf("强制指针类型换:%p\n",(int *)arg);
printf("%d\n",*(int *)arg);
}
int main()
{
int a = 3;
printf("a的地址:%p\n",&a);
test(&a);
}
运行结果:
a的地址:000000000062FE1C
arg存放的值:000000000062FE1C
强制指针类型换:000000000062FE1C
3
单源程序:
1.gcc hello.c 缺省生成a.out可执行文件
2.1 gcc -c hello.c 生成目标文件hello.o
2.2 gcc -o xxx hello.o 将目标文件链接生成的可执行文件
3.gcc -o hello hello.c 指定生成的可执行文件的名字
多源程序编译:
命令行
gcc -c 1.c 2.c 生成目标文件1.o 2.o
gcc -o hello 1.o 2.o 链接生成可执行文件
一个文件调用另一个文件
1.将被调用文件的函数声明写在调用函数中
2.被调用函数写一个.h文件
Makefile
编写makefile文件aaa@qq.com目标文件;$^所有的依赖文件;$<第一个依赖文件
静态编译和动态编译
ldd查看依赖情况
静态编译:
gcc -c xxx.c //生成.o文件;
ar -r libxxx.a xxx.o//建立静态库libxxx.a;
gcc -o name x.c -static -L. -lxxx //使用静态库编译;
动态编译:
gcc -c xxx.c //生成.o文件;
gcc -shared -fPIC -o libxxx.so xxx.c //生成动态库xxx.so;
gcc -o name x.c -L. -ltxxx //使用动态编译;
LD_LIBRARY_PATH=. ./name //指明共享库的路径;
进程和线程
- 同一进程中的多条线程将共享该进程中的全部系统资源,如虚拟地址空间,文件描述符和信号处理等等。
- 同一进程中的多个线程有各自的调用栈(call stack),自己的寄存器环境(register context),自己的线程本地存储(thread-local storage)。
- 一个进程可以有很多线程,每条线程并行执行不同的任务。
- 进程是资源分配的最小单位,线程是程序执行的最小单位。
- 进程有自己的独立地址空间,每启动一个进程,系统就会为它分配地址空间,建立数据表来维护代码段、堆栈段和数据段,这种操作非常昂贵。
- 线程是共享进程中的数据的,使用相同的地址空间,因此CPU切换一个线程的花费远比进程要小很多,同时创建一个线程的开销也比进程要小很多。
- 线程之间的通信更方便,同一进程下的线程共享全局变量、静态变量等数据.
- 进程之间的通信需要以通信的方式(IPC)进行。不过如何处理好同步与互斥是编写多线程程序的难点。
多进程
进程基础
资源分配的最小单位;
fork()函数,在父进程中返回值为子进程ID,在子进程中返回值为0,如果出现错误返回一个负数。
进程间通信
管道、命名管道;信号;消息队列;共享内存;信号量;套接字;
待补充细节。。。
多线程
程序执行的最小单位;
线程基础
1.线程创建
创建线程实际上就是确定调用该线程函数的入口点,这里通常使用的函数是pthread_create()。在线程创建后,就开始运行相关的线程函数。
int pthread_create(pthread_t *thread,const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);
2.线程退出
在线程创建后,就开始运行相关的线程函数,在该函数运行完之后,该线程也就退出了,这也是线程退出的一种方法。另一种退出线程的方法是使用函数pthread_exit(),这是线程的主动行为。这里要注意的是,在使用线程函数时,不能随意使用exit()退出函数来进行出错处理。由于exit()的作用是使调用进程终止,而一个进程往往包含多个线程,因此,在使用exit()之后,该进程中的所有线程都终止了。在线程中就可以使用pthread_exit()来代替进程中的exit()。
void pthread_exit(void *retval);
3.线程等待
由于一个进程中的多个线程是共享数据段的,因此,通常在线程退出后,退出线程所占用的资源并不会随着线程的终止而得到释放。正如进程之间可以用wait()系统调用来同步终止并释放资源一样,线程之间也有类似机制,那就是pthread_join()函数。pthread_join()用于将当前进程挂起来等待线程的结束。这个函数是一个线程阻塞的函数,调用它的函数将一直等待到被等待的线程结束为止,当函数返回时,被等待线程的资源就被收回。
int pthread_join(pthread_t thread, void **retval);
4.线程取消
前面已经提到线程调用pthread_exit()函数主动终止自身线程,但是在很多线程应用中,经常会遇到在别的线程中要终止另一个线程的问题,此时调用pthread_cancel()函数来实现这种功能,但在被取消的线程的内部需要调用pthread_setcancel()函数和pthread_setcanceltype()函数设置自己的取消状态。例如,被取消的线程接收到另一个线程的取消请求之后,是接受函数忽略这个请求;如果是接受,则再判断立刻采取终止操作还是等待某个函数的调用等。
int pthread_cancel(pthread_t thread);
5.线程的ID
pthread_t pthread_self(void);
6.线程清除
线程终止有两种情况:正常终止和非正常终止。线程主动调用pthread_exit()或者从线程函数中return都将使线程正常退出,这是可预见的退出方式;非正常终止是线程在其它线程的干预下,或者由于自身运行出错(比如访问非法地址)而退出,这种退出方式是不可预见的。不论是可预见的线程终止还是异常终止,都回存在资源释放的问题,如何保证线程终止时能顺利地释放掉自己所占用的资源,是一个必须考虑的问题。从pthread_cleanup_push()的调用点到pthread_cleanup_pop()之间的程序段中的终止动作(包括调用pthread_exit()和异常终止,不包括return)都将执行pthread_cleanup_push()所指定的清理函数。
void pthread_cleanup_push(void (*routine)(void *),void *arg);
void pthread_cleanup_pop(int execute);
线程实例
1.用pthread_create()函数创建线程的实例
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
void *mythread_1(void)
{
int i;
for(i = 0;i < 5;i++)
{
printf("this is thread_1.\n");
sleep(2);
}
}
void *mythread_2(void)
{
int i;
for(i = 0;i < 5;i++)
{
printf("this is thread_2.\n");
sleep(2);
}
}
int main(void)
{
pthread_t id1,id2;
int res;
//create a thread,let it exec thread_1
res = pthread_create(&id1,NULL,(void *)mythread_1,NULL);
if(res)
{
printf("Create thread error!\n");
return 1;
}
//create a thread,let it exec thread_2
res = pthread_create(&id2,NULL,(void *)mythread_2,NULL);
if(res)
{
printf("Create thread error!\n");
return 1;
}
//main() exit until above threads exit.
pthread_join(id1,NULL);
pthread_join(id2,NULL);
return 1;
}
运行结果
2.pthread_exit()函数退出线程的举例
#include <stdio.h>
#include <pthread.h>
//thread function
void *create(void *arg)
{
printf("New thread is created...\n");
pthread_exit((void *)"fuck");
}
int main()
{
pthread_t tid;
int res;
void *tmp;
res = pthread_create(&tid,NULL,create,NULL);
printf("I am the main thread!\n");
if(res)
{
printf("error occurs.\n");
return -1;
}
//join's argument is double pointer.
res = pthread_join(tid,&tmp);
if(res)
{
printf("error occurs.\n");
return -2;
}
printf("Thread is exit code %s\n",tmp);
return 0;
}
运行结果
3.用pthread_join()实现线程等待
#include<stdio.h>
#include<pthread.h>
#include<unistd.h>
void *thread(void *str)
{
int i;
for(i = 0;i < 4;++i)
{
sleep(1);
printf("this is the pthread:%d\n",i);
}
}
int main()
{
pthread_t tid;
int i,res;
res = pthread_create(&tid,NULL,thread,NULL);
//wait subthread finished.
pthread_join(tid,NULL);
for(i = 0;i < 3;++i)
{
sleep(1);
printf("this is the main:%d\n",i);
}
return 0;
}
运行结果
4.pthread_self()获取线程ID
#include<pthread.h>
#include<unistd.h>
void *create(void *str)
{
printf("New thread...\n");
printf("this thread'id is %d\n",(unsigned int)pthread_self());
printf("this thread process pid is %d\n",getpid());
}
int main()
{
pthread_t tid;
int res;
res = pthread_create(&tid,NULL,create,NULL);
if(res)
{
printf("error occurs!\n");
return -1;
}
printf("the main process pid is %d\n",getpid());
sleep(1);
return 0;
}
运行结果
5.线程清理函数的使用
#include <stdio.h>
#include <pthread.h>
void *clean(void *arg)
{
printf("clean up:%s \n",(char *)arg);
return (void *)0;
}
void *thread1(void *arg)
{
printf("thread 1 start \n");
//push clean_fun two times with different argument.
pthread_cleanup_push((void *)clean,"thread1 first handler");
pthread_cleanup_push((void *)clean,"thread1 second handler");
printf("thread1 push complete.\n");
if(arg) return (void *)1;
pthread_cleanup_pop(1);
pthread_cleanup_pop(1);
return (void *)2;
}
void *thread2(void *arg)
{
printf("thread 2 start \n");
pthread_cleanup_push((void *)clean,"thread2 first handler");
pthread_cleanup_push((void *)clean,"thread2 second handler");
printf("thread2 push complete.\n");
if(arg) return (void *)3;
pthread_cleanup_pop(1);
pthread_cleanup_pop(0);
return (void *)4;
}
int main()
{
int res;
pthread_t tid1,tid2;
void *tret;
res = pthread_create(&tid1,NULL,thread1,(void *)1);
if(res!=0)
{
printf("error occurs!\n");
return -1;
}
res = pthread_create(&tid2,NULL,thread2,(void *)0);
if(res!=0)
{
printf("error occurs!\n");
return -1;
}
res = pthread_join(tid1,&tret);
if(res!=0)
{
printf("error occurs!\n");
return -1;
}
printf("thread1 exit code:%d\n",tret);
res = pthread_join(tid2,&tret);
if(res!=0)
{
printf("error occurs!\n");
return -1;
}
printf("thread2 exit code:%d\n",tret);
}
//由运行截图可以看出线程2是完成了清除函数入栈、出栈的过程,线程2的第二个pop()参数导致运行了清除函数一次。
//而线程1由线程调用参数arg导致只进行了入栈之后便出栈了。
运行结果
6.多线程共用执行函数
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#define THREAD_NUMBER 3
#define REPEAT_NUMBER 5
#define DELAY_TIME_REPEAT 10.0
void *thrd_func(void *arg)
{
int thrd_num = (int)arg;
int delay_time = 0;
int count = 0;
printf("Thread %d is starting.\n",thrd_num);
for(count = 0;count < REPEAT_NUMBER;count++ )
{
delay_time = (int)(rand()*DELAY_TIME_REPEAT/(RAND_MAX))+1;
sleep(delay_time);
printf("\tThread %d:job %d delay=%d\n",thrd_num,count,delay_time);
}
printf("Thread %d finished.\n",thrd_num);
pthread_exit(NULL);
}
int main()
{
pthread_t thread[THREAD_NUMBER];
int index,res;
void *ret;
srand(time(NULL));
for(index = 0;index < THREAD_NUMBER;index++ )
{
res = pthread_create(&thread[index],NULL,(void *)thrd_func,(void *)index);
if(res!=0)
{
printf("Create thread %d failed.\n",index);
exit(res);
}
}
printf("Creating threads successfully.\nWaiting for threads to finished.\n");
for(index = 0;index < THREAD_NUMBER;index++)
{
res = pthread_join(thread[index],&ret);
if(!res) printf("Thread %d joined\n",index);
else printf("Thread %d join failed\n",index);
}
return 0;
}
//此程序建立了三个线程,通过随机生成一个时间模拟程序的运行。主程序后面建立三个线程等待函数,防止主程序先行退出。可以观察到线程的执行顺序是不定的。
运行结果
线程同步与互斥
互斥锁更适用于同时可用的资源是唯一的情况,信号量更适用于同时可用的资源为多个的情况。
互斥锁
互斥锁是用一种简单的加锁方法来控制对共享资源的原子操作。这个互斥锁只有两种状态,即上锁和解锁,可以把互斥锁看做某种意义上的全局变量。在同一个时刻只能有一个线程掌握某个互斥锁,拥有上锁状态的线程能够对共享资源进行操作。若其他线程希望上锁一个已经被上锁的互斥锁,则该线程就会被挂起,直到上锁的线程释放掉互斥锁为止。
·互斥锁初始化:int pthread_mutex_init(pthread_mutex_t* mutex,const pthread_mutexattr_t* mutexattr);
··PTHREAD_MUTEX_INITIALIZER //创建快速互斥锁
·互斥锁上锁:pthread_mutex_lock(pthread_mutex_t *mutex)
·互斥锁判断上锁:pthread_mutex_trylock(pthread_mutex_t *mutex)
·互斥锁解锁:pthread_mutex_unlock(pthread_mutex_t *mutex)
·消除互斥锁:pthread_mutex_destroy(pthread_mutex_t *mutex)
将上述多线程公用执行函数的例子修改,运行结果
//main函数中加一个初始化锁和释放锁,执行函数中加锁,一个全局化变量;
//global variable
pthread_mutex_t mutex;
//mutex lock
int res;
res = pthread_mutex_lock(&mutex);
if(res)
{
printf("Thread %d lock fail\n",thrd_num);
pthread_exit(NULL);
}
//mutex init
pthread_mutex_init(&mutex,NULL);
//mutex unlock
pthread_mutex_unlock(&mutex);
信号量
信号量就是操作系统中多用到的PV原子操作,它广泛应用于进程或线程间的同步与互斥。PV原子操作主要用于进程或线程间的同步和互斥这两种典型情况。若用于互斥,几个进程(或线程)往往只设置一个信号量sem。当信号量用于同步操作时,往往会设置多个信号量,并安排不同的初始值来实现它们之间的顺序执行。运行结果
·初始化信号量 sem_init(sem_t *sem,int pshared,unsigned int value)
·P操作 sem_wait(sem_t *sem)
·V操作 sem_post(sem_t *sem)
·获取信号量的值 sem_getvalue(sem_t *sem)
·删除信号量 sem_destroy(sem_t *sem)
//一个列子,主进程等待文本输入,子线程输出统计信息。使用两个信号量sem(初试为0)和sem_add(初试为1)
//主进程等待文本输入后,sem_add变为0,sem变为1。
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
sem_t sem;//信号量
sem_t sem_add;//增加的信号量
#define MSG_SIZE 512
void* thread_func(void *msg)
{
char *ptr = msg;
//信号量减1
sem_wait(&sem);
while(strcmp("end\n", msg) != 0)
{
int i = 0;
//把小写字母变成大写
for(; ptr[i] != '\0'; ++i)
{
if(ptr[i] >= 'a' && ptr[i] <= 'z')
{
ptr[i] -= 'a' - 'A';
}
}
printf("You input %d characters\n", i-1);
printf("To Uppercase: %s\n", ptr);
//信号量加1,表示子线程处理完成
sem_post(&sem_add);
//信号量减1,等待主进程处理
sem_wait(&sem);
}
sem_post(&sem_add);
//退出线程
pthread_exit(NULL);
}
int main()
{
int res = -1;
pthread_t thread;
void *thread_result = NULL;
char msg[MSG_SIZE];
//初始化信号量,初始值为0
res = sem_init(&sem, 0, 0);
if(res == -1)
{
perror("semaphore intitialization failed\n");
exit(EXIT_FAILURE);
}
//初始化信号量,初始值为1
res = sem_init(&sem_add, 0, 1);
if(res == -1)
{
perror("semaphore intitialization failed\n");
exit(EXIT_FAILURE);
}
//创建线程,并把msg作为线程函数的参数
res = pthread_create(&thread, NULL, thread_func, msg);
if(res != 0)
{
perror("pthread_create failed\n");
exit(EXIT_FAILURE);
}
//输入信息,以输入end结束,由于fgets会把回车(\n)也读入,所以判断时就变成了“end\n”
printf("Input some text. Enter 'end'to finish...\n");
sem_wait(&sem_add);
while(strcmp("end\n", msg) != 0)
{
fgets(msg, MSG_SIZE, stdin);
//把信号量加1,子线程开始处理
sem_post(&sem);
//把sem_add的值减1,即等待子线程处理完成
sem_wait(&sem_add);
}
printf("Waiting for thread to finish...\n");
//等待子线程结束
res = pthread_join(thread, &thread_result);
if(res != 0)
{
perror("pthread_join failed\n");
exit(EXIT_FAILURE);
}
printf("Thread joined\n");
//清理信号量
sem_destroy(&sem);
sem_destroy(&sem_add);
exit(EXIT_SUCCESS);
}
I/O多路复用
select()
int select(int nfds,fd_set *readfds,fd_set *writefds,fd_set *exceptfds,struct timeval *timeout);
int nfds:描述符个数(最大描述符+1)
fd_set *readfds:内核读的描述符
fd_set *writefds:内核写的描述符
fd_set *exceptfds:内核异常条件的描述符
struct timeval *timeout:内核等待所指定描述字中的任何一个就绪可花多少时间
适用场景
- 当客户处理多个描述字时(一般是交互式输入和网络套接口),必须使用I/O复用。
- 当一个客户同时处理多个套接口时,而这种情况是可能的,但很少出现。
- 如果一个TCP服务器既要处理监听套接口,又要处理已连接套接口,一般也要用到I/O复用。
- 如果一个服务器即要处理TCP,又要处理UDP,一般要使用I/O复用。
- 如果一个服务器要处理多个服务或多个协议,一般要使用I/O复用。
优势
I/O多路复用技术的最大优势是系统开销小,系统不必创建进程/线程,也不必维护这些进程/线程,从而大大减小了系统的开销。
一个例子
以后再补充。。。
//server.c
//client.c
参考链接:
https://blog.csdn.net/mybelief321/article/details/9377379