【Linux Gcc】vim基操、多线程、多进程总结

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
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
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可执行文件【Linux Gcc】vim基操、多线程、多进程总结_第1张图片
2.1 gcc -c hello.c    生成目标文件hello.o
2.2 gcc -o xxx hello.o  将目标文件链接生成的可执行文件【Linux Gcc】vim基操、多线程、多进程总结_第2张图片
3. gcc -o hello hello.c  指定生成的可执行文件的名字【Linux Gcc】vim基操、多线程、多进程总结_第3张图片

多源程序编译:

命令行

gcc -c 1.c 2.c 生成目标文件1.o 2.o
gcc -o hello 1.o 2.o 链接生成可执行文件
一个文件调用另一个文件
1.将被调用文件的函数声明写在调用函数中
【Linux Gcc】vim基操、多线程、多进程总结_第4张图片
2.被调用函数写一个.h文件
【Linux Gcc】vim基操、多线程、多进程总结_第5张图片

Makefile

编写makefile文件
$@目标文件;$^所有的依赖文件;$<第一个依赖文件【Linux Gcc】vim基操、多线程、多进程总结_第6张图片

静态编译和动态编译

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 //指明共享库的路径;

进程和线程

  1. 同一进程中的多条线程将共享该进程中的全部系统资源,如虚拟地址空间,文件描述符和信号处理等等。
  2. 同一进程中的多个线程有各自的调用栈(call stack),自己的寄存器环境(register context),自己的线程本地存储(thread-local storage)。
  3. 一个进程可以有很多线程,每条线程并行执行不同的任务。
  4. 进程是资源分配的最小单位,线程是程序执行的最小单位。
  5. 进程有自己的独立地址空间,每启动一个进程,系统就会为它分配地址空间,建立数据表来维护代码段、堆栈段和数据段,这种操作非常昂贵。
  6. 线程是共享进程中的数据的,使用相同的地址空间,因此CPU切换一个线程的花费远比进程要小很多,同时创建一个线程的开销也比进程要小很多。
  7. 线程之间的通信更方便,同一进程下的线程共享全局变量、静态变量等数据.
  8. 进程之间的通信需要以通信的方式(IPC)进行。不过如何处理好同步与互斥是编写多线程程序的难点。

多进程

进程基础

资源分配的最小单位;
fork()函数,在父进程中返回值为子进程ID,在子进程中返回值为0,如果出现错误返回一个负数。【Linux Gcc】vim基操、多线程、多进程总结_第7张图片

进程间通信

管道、命名管道;信号;消息队列;共享内存;信号量;套接字;
待补充细节。。。

多线程

程序执行的最小单位;

线程基础

1.线程创建【Linux Gcc】vim基操、多线程、多进程总结_第8张图片
创建线程实际上就是确定调用该线程函数的入口点,这里通常使用的函数是pthread_create()。在线程创建后,就开始运行相关的线程函数。

int pthread_create(pthread_t *thread,const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);

2.线程退出8
在线程创建后,就开始运行相关的线程函数,在该函数运行完之后,该线程也就退出了,这也是线程退出的一种方法。另一种退出线程的方法是使用函数pthread_exit(),这是线程的主动行为。这里要注意的是,在使用线程函数时,不能随意使用exit()退出函数来进行出错处理。由于exit()的作用是使调用进程终止,而一个进程往往包含多个线程,因此,在使用exit()之后,该进程中的所有线程都终止了。在线程中就可以使用pthread_exit()来代替进程中的exit()。

void pthread_exit(void *retval);

3.线程等待【Linux Gcc】vim基操、多线程、多进程总结_第9张图片
由于一个进程中的多个线程是共享数据段的,因此,通常在线程退出后,退出线程所占用的资源并不会随着线程的终止而得到释放。正如进程之间可以用wait()系统调用来同步终止并释放资源一样,线程之间也有类似机制,那就是pthread_join()函数。pthread_join()用于将当前进程挂起来等待线程的结束。这个函数是一个线程阻塞的函数,调用它的函数将一直等待到被等待的线程结束为止,当函数返回时,被等待线程的资源就被收回。

int pthread_join(pthread_t thread, void **retval);

4.线程取消【Linux Gcc】vim基操、多线程、多进程总结_第10张图片
前面已经提到线程调用pthread_exit()函数主动终止自身线程,但是在很多线程应用中,经常会遇到在别的线程中要终止另一个线程的问题,此时调用pthread_cancel()函数来实现这种功能,但在被取消的线程的内部需要调用pthread_setcancel()函数和pthread_setcanceltype()函数设置自己的取消状态。例如,被取消的线程接收到另一个线程的取消请求之后,是接受函数忽略这个请求;如果是接受,则再判断立刻采取终止操作还是等待某个函数的调用等。

int pthread_cancel(pthread_t thread);

5.线程的ID【Linux Gcc】vim基操、多线程、多进程总结_第11张图片

pthread_t pthread_self(void);

6.线程清除【Linux Gcc】vim基操、多线程、多进程总结_第12张图片
线程终止有两种情况:正常终止和非正常终止。线程主动调用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 
#include 
#include 
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;
}

运行结果【Linux Gcc】vim基操、多线程、多进程总结_第13张图片
2.pthread_exit()函数退出线程的举例

#include 
#include 

//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;
}

运行结果【Linux Gcc】vim基操、多线程、多进程总结_第14张图片
3.用pthread_join()实现线程等待

#include
#include
#include
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;
}

运行结果【Linux Gcc】vim基操、多线程、多进程总结_第15张图片
4.pthread_self()获取线程ID

#include
#include
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;
}

运行结果【Linux Gcc】vim基操、多线程、多进程总结_第16张图片
5.线程清理函数的使用

#include 
#include 

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导致只进行了入栈之后便出栈了。

运行结果【Linux Gcc】vim基操、多线程、多进程总结_第17张图片
6.多线程共用执行函数

#include 
#include 
#include 
#include 

#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;
}
//此程序建立了三个线程,通过随机生成一个时间模拟程序的运行。主程序后面建立三个线程等待函数,防止主程序先行退出。可以观察到线程的执行顺序是不定的。

运行结果【Linux Gcc】vim基操、多线程、多进程总结_第18张图片

线程同步与互斥

互斥锁更适用于同时可用的资源是唯一的情况,信号量更适用于同时可用的资源为多个的情况。

互斥锁

互斥锁是用一种简单的加锁方法来控制对共享资源的原子操作。这个互斥锁只有两种状态,即上锁和解锁,可以把互斥锁看做某种意义上的全局变量。在同一个时刻只能有一个线程掌握某个互斥锁,拥有上锁状态的线程能够对共享资源进行操作。若其他线程希望上锁一个已经被上锁的互斥锁,则该线程就会被挂起,直到上锁的线程释放掉互斥锁为止。

·互斥锁初始化: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)

将上述多线程公用执行函数的例子修改,运行结果【Linux Gcc】vim基操、多线程、多进程总结_第19张图片

//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。当信号量用于同步操作时,往往会设置多个信号量,并安排不同的初始值来实现它们之间的顺序执行。运行结果【Linux Gcc】vim基操、多线程、多进程总结_第20张图片

·初始化信号量 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 
#include 
#include 
#include 
#include 
#include 
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:内核等待所指定描述字中的任何一个就绪可花多少时间

适用场景

  1. 当客户处理多个描述字时(一般是交互式输入和网络套接口),必须使用I/O复用。
  2. 当一个客户同时处理多个套接口时,而这种情况是可能的,但很少出现。
  3. 如果一个TCP服务器既要处理监听套接口,又要处理已连接套接口,一般也要用到I/O复用。
  4. 如果一个服务器即要处理TCP,又要处理UDP,一般要使用I/O复用。
  5. 如果一个服务器要处理多个服务或多个协议,一般要使用I/O复用。

优势

I/O多路复用技术的最大优势是系统开销小,系统不必创建进程/线程,也不必维护这些进程/线程,从而大大减小了系统的开销。

一个例子

以后再补充。。。

//server.c

//client.c

参考链接:
https://blog.csdn.net/mybelief321/article/details/9377379

你可能感兴趣的:(代码艺术)