网站到期续费通知,青岛高端网站制作公司,专门做婚纱儿童摄影网站,自己做网站用花钱吗1.进程的缺陷和线程的优点
1.进程的缺陷
创建进程#xff08;复制#xff09;的工作本身会给操作系统带来相当沉重的负担。
而且#xff0c;每个进程具有独立的内存空间#xff0c;所以进程间通信的实现难度也会随之提高。
同时#xff0c;上下文切换#xff08;Cont…1.进程的缺陷和线程的优点
1.进程的缺陷
创建进程复制的工作本身会给操作系统带来相当沉重的负担。
而且每个进程具有独立的内存空间所以进程间通信的实现难度也会随之提高。
同时上下文切换Context Switching过程是创建进程的最大开销。
系统同时运行多个进程系统将CPU时间分成多个微小的块后分配给了多个进程。为了分时使用CPU,需要上下文切换过程。下面了解一下“上下文切换”的概念。
如果运行进程A后需要紧接着运行进程B就应该将进程A相关信息移出内存并读入进程B相关信息。这就是上下文切换。但此时进程A的数据将被移动到硬盘所以上下文切换需要很长时间。即使通过优化加快速度也会存在一定的局限。
2.线程的优点
为了保持多进程的优点同时在一定程度上克服其缺点人们引人了线程(Thread )。这是为了将进程的各种劣势降至最低限度不是直接消除而设计的一种“轻量级进程”。线程相比于进程具有如下优点。 1.线程的创建和上下文切换比进程的创建和上下文切换更快。
2.线程间交换数据时无需特殊技术。 2.线程和进程的差异
进程空间
每个进程的内存空间都由保存全局变量的“数据区”、向malloc等函数的动态分配提供空间的堆Heap)、函数运行时使用的栈(Stack)构成。每个进程都拥有这种独立空间如下 如果以获得多个代码执行流为主要目的则不应像上面一样完全分离内存结构而只需要分离栈区域。因此可以通过这种办法获取下面的优势
1.上下文切换时不需要切换数据区和堆
2.可以利用数据区和堆交换数据
线程为了保持多条代码执行流而隔开了栈区域因此拥有下面的内存结构 多个线程将共享数据区和堆。为了保持这种结构线程将在进程内创建并运 线程将在进程内创建并运行。也就是说,进程和线程可以定义为如下形式。
进程:在操作系统构成单独执行流的单位。
线程:在进程构成单独执行流的单位。 3.线程创建及运行
POSIX是Portable Operating System Interface for Computer Environment(适用于计算机环境的可移植操作系统接口)的简写,是为了提高UNIX系列操作系统间的移植性而制定的API规范。下面要介绍的线程创建方法也是以POSIX标准为依据的。因此它不仅适用于Linux也适用于大部分UNIX系列的操作系统。
1.线程的创建和执行流程
下面是我在百度找到的关于restrict关键字的描述
restrictC语言中的一种类型限定符Type Qualifiers用于告诉编译器对象已经被指针所引用不能通过除该指针外所有其他直接或间接的方式修改该对象的内容。
1.pthread_create函数
int pthread_create(pthread_t *restrict thread,const pthread_attr_t *restrict attr,void* (* start_routine)(void*),void *restrict arg);
//成功时返回0失败时返回其他值
1thread
保存新创建线程ID的变量地址值。线程与进程相同也需要用于区分不同线程的ID。
2attr
用于传递线程属性的参数传递NULL时创建默认属性的线程
3start_routine
相当于线程main函数的、在单独执行流中执行的函数地址值函数指针
4arg
通过第三个参数传递调用函数所包含传递参数信息的变量地址值
代码示例
#includestdio.h
#includepthread.hvoid* thread_main(void *arg)
{int cnt*((int*)arg);int i;for(i0;icnt;i){sleep(1);puts(running thread);}return NULL;
}int main(int argc,char *argv[])
{pthread_t trdId;int threadParam5;if(pthread_create(trdId,NULL,thread_main,(void*)threadParam)!0){puts(pthread_create() error);return -1;}sleep(10);puts(end of main);return 0;
}
线程相关代码在编译时需要添加-lpthread选项声明需要连接线程库只有这样才能调用pthread.h中声明的函数。 虚线代表执行流程向下的箭头指的是执行流横向箭头是函数调用。 如果将sleep(10)改成sleep(2)不会像代码中写的那样输出5次“running thread”字符串如上。因为main函数返回后整个进程将被销毁。如下 那是否就只要合理使用sleep函数就能很好的控制线程的执行了呢
通过调用sleep函数控制线程的执行相当于预测程序的执行流程但实际上这是不可能完成的。 而且稍有不慎很可能干扰程序的正常执行流。
因此为了控制线程的执行流通常利用下面的函数
2.pthread_join函数
int pthread_join(pthread_t thread,void** status);
//成功返回0失败时返回其他值
1thread
该参数值ID的线程终止后才会从该函数返回
2status
保存线程的main函数返回值的指针变量地址值
也就是说调用该函数的进程或者线程将进入等待状态直到ID为第一个参数的线程终止为止。
而且可以得到线程的main函数返回值。代码示例
#includestdio.h
#includestdlib.h
#includestring.h
#includepthread.h
void *thread_main(void *arg)
{int cnt*((int*)arg);char *msg(char*)malloc(sizeof(char)*50);strcpy(msg,Hello, I am thread.\n);int i;for(i0;icnt;i){sleep(1);puts(running thread);}return (void*)msg;
}int main(int argc,char *argv[])
{pthread_t trd_id;int thread_param5;if(pthread_create(trd_id,NULL,thread_main,(void*)thread_param)!0){puts(pthread_create() error);return -1;}void* pthread_rtn;if(pthread_join(trd_id,pthread_rtn)!0){puts(pthread_join() error);return -1;}printf(Thread return message: %s\n,(char*)pthread_rtn);free(pthread_rtn);return 0;
} 其执行流程图如下 2.临界区
关于线程的运行需要考虑多个线程同时调用函数时可能产生问题。
这类函数内部存在临界区Critical Section也就是说多个线程同时执行这部分代码可能引发问题。
临界区指的是一个访问共用资源的程序片段而这些共用资源又无法同时被多个线程访问的特性。
当有线程进入临界区段时其他线程或是进程必须等待有一些同步的机制必须在临界区段的进入点与离开点实现以确保这些共用资源是被互斥获得使用。
因此根据临界区是否引起问题函数可分为以下2类
1.线程安全函数Thread-safe function
2.非线程安全函数Thread-unsafe function
线程安全函数被多个线程同时调用时也不会引发问题。反之非线程安全函数被同时调用时会引发问题。
但这并非关于有无临界区的讨论线程安全的函数中同样可能存在临界区。只是在线程安全函数中同时被多个线程调用时可通过一些措施避免问题。
大多数标准函数都是线程安全的函数。而且不需要自己区分线程安全的函数和非线程安全的函数在Windows程序中同样如此)。因为这些平台在定义非线程安全函数的同时提供了具有相同功能的线程安全的函数。如 提供线程安全的同一函数是 线程安全函数的名称后缀通常为_r(这与Windows平台不同。但这种方法会给程序员带来沉重的负担。可以通过声明头文件前定义_REENTRANT宏。自动将gethostbyname函数调用改为gethostbyname_r的函数调用。
gethostbyname函数和gethostbyname_r函数的函数名和参数声明都不同。因此这种宏声明方式很有用。另外无需为了上述宏定义特意添加#define语句可以在编译时通过添加-D REENTRANT选项定义宏。另外无需为了上述宏定义特意添加#定义语句可以在编译时通过添加-D_REENTRANT可重入的选项定义宏。如下 下面编译线程相关代码时均默认添加-D_REENTRANT选项。 3.工作Worker线程模型
下面给出创建多个线程的示例
计算1到10的和但并不是在main函数中进行累加运算,而是创建2个线程,其中一个线程计算1到5的和另一个线程计算6到10的和main函数只负责输出运算结果。
这种方式的编程模型称为工作线程Worker thread)模型。计算1到5之和的线程与计算6到10之和的线程将成为main线程管理的工作(Worker )这里是不是应该这样断句main 线程管理求大佬告知。最后给出示例代码前先给出程序执行流程图 代码示例
#includestdio.h
#includepthread.hint sum0;
void *thread_summation(void *arg)
{int start((int*)arg)[0];int end((int*)arg)[1];while(startend){sumstart;start;}return NULL;
}int main(int argc,char *argv[])
{int range1[]{1,5};int range2[]{6,10};pthread_t trd_id1,trd_id2;pthread_create(trd_id1,NULL,thread_summation,(void*)range1);pthread_create(trd_id2,NULL,thread_summation,(void*)range2);void *trd_rtn1,*trd_rtn2;pthread_join(trd_id1,trd_rtn1);pthread_join(trd_id2,trd_rtn2);printf(sum%d\n,sum);return 0;
}
结果 虽然结果正确但2个线程直接访问了全局变量sum。因此存在着问题下面举相似的例子证明
#includestdlib.h
#includeunistd.h#define NUM_THREAD 100long long num0;void *thread_inc(void *arg)
{int i;for(i0;i50000000;i)num;return NULL;
}
void *thread_des(void *arg)
{int i;for(i0;i50000000;i)num--;return NULL;
}int main(int argc,char *argv[])
{pthread_t thread_id[NUM_THREAD];printf(sizeof long long: %d\n,sizeof(long long));int i;for(i0;iNUM_THREAD;i){if(i%2)pthread_create(thread_idi,NULL,thread_inc,NULL);elsepthread_create(thread_idi,NULL,thread_des,NULL);}for(i0;iNUM_THREAD;i)pthread_join(thread_id[i],NULL);printf(sum%lld\n,num);return 0;
}
在程序中我们两个线程main函数分别加减50000000但最后的结果却不是0。 下面分析错误的原因。
上面的问题出在2个线程正在同时访问全局变量num。此处的访问指的是值的更改。
被访问的是全局变量但这并非是全局变量引发的问题。实际上任何内存空间只要被同时访问都可能发生问题。线程虽然是分时使用CPU但同时访问可能和我们所想的不一样。
最理想的情况
如下2个线程要执行将变量值逐次加1的工作 线程1将变量的值增加到100线程2再访问num时变量num中将按照我们预想保存101。 注意值的增加方式值的增加需要CPU运算完成变量中的值不会自动增加。
线程1首先读该变量的值并将其传递到CPU获得加1之后的结果为100最后把结果写回变量num这样num中就保存100。接下来给出线程2的执行过程 变量中将保存101但这是最理想的情况。线程1完全增加num值之前线程2完全有可能通过切换得到CPU资源。
可能的情况 线程1读取变量num的值并完成加1运算但这时加1后的结果尚未写入到变量num。 在将要把100保存到变量中但执行该操作前执行流程转到了线程2。
线程2获取此时num的值仍未99因为线程1还没有把num加一后的值保存到num所以线程2重复了线程1的工作线程2完成后才为100。不控制其他线程访问实际的情况可能更复杂。
因此线程访问变量num时应阻止其他线程访问直到线程1结束。这就是同步Synchronization。
因此为了保证同步需要划分临界区。 临界区并非num的定义部分而是访问num的2条语句。这2条语句可能由多个线程同时使用也是引起问题的直接原因。产生的问题可以整理为3中情况
1.2个线程同时执行thread_inc函数
2.2个线程同时执行thread_des函数
3.2个线程分别执行thread_inc函数和thread_des函数 4.线程同步
线程同步可以从下面俩方面考虑
1.同时访问同一内存空间时发生的情况
2.需要指定访问同一内存空间的线程执行顺序的情况
上面已讨论过第一种情况因此下面重点讨论第二种。这是控制线程执行顺序的相关内容。
假设有A、B两个线程线程A负责向指定内存空间写人数据线程B负责取走该数据。这种情况下线程A首先应该访问约定的内存空间并保存数据。万一线程B先访问并取走数据将导致错误结果。像这种需要控制执行顺序的情况也需要使用同步技术。
下面将会介绍互斥量Mutex和信号量Semaphore这两种同步技术。
1.互斥量
下面举一个例子
假如有个电话亭我们想要使用则必须保证里面现在没有人在使用。因此电话亭这个公共的资源不能被多个人占用。则电话亭使用规则如下
1.为了保护个人隐私电话亭在有人进来使用时会自动锁上门。
2.如果此时有人在使用电话亭则需要等待。
3.里面的人使用后电话亭从里面打开且电话亭的锁将会打开直到下一个人的使用。
因此互斥量的作用就是电话亭的门锁。
1.pthread_mutex_init函数
int pthread_mutex_init(pthread_mutex_t *mutex,const pthread_mutexattr_t *attr);
//成功返回0失败返回其他值
1mutex
创建互斥量时传递保存互斥量的变量地址值
2attr
传递即将创建的互斥量属性没有特别需要指定的属性时传递NULL。
attr可能的值有
1.PTHREAD_MUTEX_TIMED_NP这是缺省值也就是普通锁。当一个线程加锁以后其余请求锁的线程将形成一个等待队列并在解锁后按优先级获得锁。这种锁策略保证了资源分配的公平性。 2.PTHREAD_MUTEX_RECURSIVE_NP嵌套锁允许同一个线程对同一个锁成功获得多次并通过多次unlock解锁。如果是不同线程请求则在加锁线程解锁时重新竞争。 3.PTHREAD_MUTEX_ERRORCHECK_NP检错锁如果同一个线程请求同一个锁则返回EDEADLK否则与PTHREAD_MUTEX_TIMED_NP类型动作相同。这样保证当不允许多次加锁时不出现最简单情况下的死锁。 4.PTHREAD_MUTEX_ADAPTIVE_NP适应锁动作最简单的锁类型仅等待解锁后重新竞争。 2.pthread_mutex_destory函数
int pthread_mutex_destory(pthread_mutex_t *mutex);
//成功返回0失败返回其他值
1mutex
需要销毁的互斥量地址值 为了创建相当于锁系统的互斥量需要声明如下pthread_mutex_t变量 该变量的地址将传递给pthread_mutex_init函数用来保存操作系统创建的互斥量锁系统。
调用pthread_mutex_destory函数时同样需要该信息。如果不需要配置特殊的互斥量属性则向第二个参数传递NULL时可以利用PTHREAD_MUTEX_INITIALIZER宏进行如下初始化 但通过宏进行初始化很难发现发生的错误因此上面的做法不推荐最好用pthread_init函数初始化。
3.pthread_mutex_lock函数
int pthread_mutex_lock(pthread_mutex_t *mutex);
//成功返回0失败时返回其他值
4.pthread_mutex_unlock函数
int pthread_mutex_unlock(pthread_mutex_t *mutex);
//成功返回0失败时返回其他值 函数的名字很容易理解。进入临界区前调用的函数就是pthread_mutex_lock函数。相当于进入锁门的过程。因为如果里面有人则我们将不能进入使用电话亭。所以调用该函数时如果发现其他线程已进入临界区则pthread_mutex_lock函数不会返回直到里面的线程调用pthread_mutex_unlock退出临界区为止。也就是其他线程让出临界区之前当前线程将一直处于阻塞状态。创建好互斥量的前提下可以通过如下结构保护临界区 退出临界区时如果线程忘了调用pthread_mutex_unlock函数那么其他为了进入临界区而调用pthread_mutex_lock函数的线程就无法摆脱阻塞状态。这种情况称为死锁Dead-lock。
#includestdio.h
#includepthread.h
#includestdlib.h
#includeunistd.h#define NUM_THREAD 100long long num0;
pthread_mutex_t mutex;void *thread_inc(void *arg)
{int i;pthread_mutex_lock(mutex);for(i0;i50000000;i)num;pthread_mutex_unlock(mutex);return NULL;
}
void *thread_des(void *arg)
{int i;pthread_mutex_lock(mutex);for(i0;i50000000;i)num--;pthread_mutex_unlock(mutex);return NULL;
}
int main(int argc,char *argv[])
{pthread_t thread_id[NUM_THREAD];printf(sizeof long long: %d\n,sizeof(long long));pthread_mutex_init(mutex,NULL);int i;for(i0;iNUM_THREAD;i){if(i%2)pthread_create(thread_idi,NULL,thread_inc,NULL);elsepthread_create(thread_idi,NULL,thread_des,NULL);}for(i0;iNUM_THREAD;i)pthread_join(thread_id[i],NULL);printf(sum%lld\n,num);pthread_mutex_destroy(mutex);return 0;
}
结果 2.信号量
此处只涉及二进制信号量完成控制线程顺序为中心的同步方法。
1.sem_init函数
int sem_init(sem_t *sem,int pshared,unsigned int value);
//成功返回0失败返回其他值
1sem
创建信号量时传递信号量的变量地址值
2pshared
传递其他值时创建可由多个进程共享的信号量;传递0时创建只允许1个进程内部使用的信号量。我们需要完成同一进程内的线程同步故传递0。
3value
指定新创建的信号量初始值 2.sem_destroy函数
int sem_destroy(sem_t *sem);
//成功时返回0失败时返回其他值
1sem
传递需要销毁的信号量的变量地址值 3.sem_post函数
int sem_post(sem_t *sem);
//成功返回0失败返回其他值
1sem
传递保存信号量读取值的变量地址值传递给sem_post时信号量增1。 4.sem_wait函数
int sem_wait(sem_t *sem);
//成功时返回0失败返回其他值
1sem
传递保存信号量读取值的变量地址值传递给sem_wait时信号量减1。 调用sem_init函数时操作系统将创建信号量对象此对象中记录着“信号量值”Semaphore Value整数。
该值在调用sem_post函数时增1调用sem_wait函数时减1。但信号量的值不能小于0因此在信号量为0的情况下调用sem_wait函数时调用函数的线程将进入阻塞状态因为函数未返回)。当然此时如果有其他线程调用sem_post函数信号量的值将变为1而原本阻塞的线程可以将该信号量重新减为0并跳出阻塞状态。实际上就是通过这种特性完成临界区的同步操作可以通过如下形式同步临界区假设信号量的初始值为1)。 调用sem_wait函数进入临界区的线程在调用sem_post函数前不允许其他线程进入临界区。
信号量的值在0和1之间跳转因此具有这种特性的机制称为二进制信号量。
下面给出一个示例
线程A从用户输入得到值后存入全局变量num此时线程B将取走该值并累加。
该过程共进行5次,完成后输出总和并退出程序。
#includestdio.h
#includepthread.h
#includesemaphore.hstatic sem_t sem_one;
static sem_t sem_two;
static int num;void* read(void *arg)
{int i;for(i0;i5;i){fputs(Input num: ,stdout);sem_wait(sem_two);scanf(%d,num);sem_post(sem_one);}return NULL;
}void* accu(void *arg)
{int sum0;int i;for(i0;i5;i){sem_wait(sem_one);sumnum;sem_post(sem_two);}printf(Result: %d \n,sum);return NULL;
}int main(int argc,char *argv[])
{sem_init(sem_one,0,0);sem_init(sem_two,0,1);pthread_t id_t1,id_t2;pthread_create(id_t1,NULL,read,NULL);pthread_create(id_t2,NULL,accu,NULL);pthread_join(id_t1,NULL);pthread_join(id_t2,NULL);sem_destroy(sem_one);sem_destroy(sem_two);return 0;
}
结果 4.线程的销毁
销毁线程有以下的三种方法书上似乎只说了两种方法第一种不知道算不算大家帮看看
1.Linux线程并不是在首次调用的线程main函数返回时自动销毁。
2.pthread_join函数
3.pthread_detach函数 pthread_join函数很好理解就是等待线程终止还会引导线程销毁。
1.pthread_detach函数
int pthread_detach(pthread_t pthread);
//成功返回0失败时返回其他值
1thread
终止的同时需要销毁的进程ID 调用该函数不会引起线程终止或进入阻塞状态可以通过该函数引导销毁线程创建的内存空间。
另外调用该函数后不能再针对相应线程调用pthread_join函数。
一个简单的聊天小程序示例
服务器接收用户发送的信息然后将其发送给参与聊天的全部用户。客户端发送信息接收服务端发送的信息。类似一个qq群的作用。
服务器端
#includestdio.h
#includepthread.h
#includeunistd.h
#includesys/socket.h
#includearpa/inet.h
#includestdlib.h
#includestring.h#define BUF_SIZE 100
#define MAX_CLNT 256int clientCnt0;
int clientSocks[MAX_CLNT];
pthread_mutex_t mutx;//send to all
void send_msg(char *msg,int len)
{ int i;pthread_mutex_lock(mutx);for(i0;iclientCnt;i)write(clientSocks[i],msg,len);pthread_mutex_unlock(mutx);
}void *handle_clnt(void *arg)
{ int clientSock*((int*)arg);int readLen0;char msg[BUF_SIZE];int i;while((readLenread(clientSock,msg,sizeof(msg)))!0)send_msg(msg,readLen);pthread_mutex_lock(mutx);//remove disconnected clientfor(i0;iclientCnt;i){ if(clientCntclientSocks[i]){while(iclientCnt-1){clientSocks[i]clientSocks[i1];}break;}}clientCnt--;pthread_mutex_unlock(mutx);close(clientSock);return NULL;
}void printMess(char *mess)
{fputs(mess,stderr);fputc(\n,stderr);exit(1);
}int main(int argc,char *argv[])
{if(argc!3)printMess(argc error);pthread_mutex_init(mutx,NULL);int serverSocksocket(PF_INET,SOCK_STREAM,IPPROTO_TCP);struct sockaddr_in serverAddr;memset(serverAddr,0,sizeof(serverAddr));serverAddr.sin_familyAF_INET;serverAddr.sin_porthtons(atoi(argv[1]));serverAddr.sin_addr.s_addrinet_addr(argv[2]);if(bind(serverSock,(struct sockaddr*)serverAddr,sizeof(serverAddr))-1)printMess(bind() error!);if(listen(serverSock,5)-1)printMess(listen() error!);struct sockaddr_in clientAddr;pthread_t id1;socklen_t clientAddrLensizeof(clientAddr);while(1){int clientSockaccept(serverSock,(struct sockaddr*)clientAddr,clientAddrLen);pthread_mutex_lock(mutx);clientSocks[clientCnt]clientSock;pthread_mutex_unlock(mutx);pthread_create(id1,NULL,handle_clnt,(void*)clientSock);pthread_detach(id1);printf(Connected client IP: %s \n,inet_ntoa(clientAddr.sin_addr));}close(serverSock);return 0;
} 客户端1
#includestdio.h
#includepthread.h
#includestdlib.h
#includestring.h
#includesys/socket.h
#includearpa/inet.h#define BUF_SIZE 100
#define NAME_SIZE 20char name[NAME_SIZE][DEFAULT];
char msg[BUF_SIZE];void *send_msg(void *arg)
{int sock*((int*)arg);char name_msg[NAME_SIZEBUF_SIZE];while(1){fgets(msg,BUF_SIZE,stdin);if(!strcmp(msg,q\n)||!strcmp(msg,Q\n)){close(sock);exit(0);}sprintf(name_msg,%s %s,name,msg);write(sock,name_msg,strlen(name_msg));}return NULL;
}void *recv_msg(void* arg)
{int sock*((int*)arg);char name_msg[NAME_SIZEBUF_SIZE];int readLen0;int error_rtn-1;while(1){readLenread(sock,name_msg,NAME_SIZEBUF_SIZE-1);if(readLen-1)return (void*)(-1);name_msg[readLen]0;fputs(name_msg,stdout);}return NULL;
}void printMess(char *mess)
{fputs(mess,stderr);fputc(\n,stderr);exit(1);
}int main(int argc,char *argv[])
{if(argc!4)printMess(argc error!);sprintf(name,[%s],argv[1]);int clientSocksocket(PF_INET,SOCK_STREAM,IPPROTO_TCP);struct sockaddr_in serverAddr;memset(serverAddr,0,sizeof(serverAddr));serverAddr.sin_familyAF_INET;serverAddr.sin_porthtons(atoi(argv[2]));serverAddr.sin_addr.s_addrinet_addr(argv[3]);if(connect(clientSock,(struct sockaddr*)serverAddr,sizeof(serverAddr))-1)printMess(connect() error!);pthread_t snd_thread,rcv_thread;pthread_create(snd_thread,NULL,send_msg,(void*)clientSock);pthread_create(rcv_thread,NULL,recv_msg,(void*)clientSock);pthread_join(snd_thread,NULL);pthread_join(rcv_thread,NULL);close(clientSock);return 0;
} 客户端2