头文件
#include <pthread.h>
Compile and link with -pthread.
gcc编译的时候一定要加**-lpthread**选项告诉链接器在链接的时候要连接pthread库。
1. 关于线程的常用函数及简单说明
int pthread_create(
pthread_t *restrict tidp,
const pthread_attr_t *restrict attr,
void *(*start_rtn)(void *),
void *restrict arg
);
功能:创建线程
参数:
- 第一个参数为pthread_t类型的指针,使用之前一定要先申明;此参数是新创建的线程ID指向的内存单元。
- 第二个参数用来设置线程属性,其类型是 pthread_attr_t 结构体类型,其定义如下所示。这个参数可以设为NULL,即创建后默认是joinable状态。
- 第三个参数是线程运行函数的地址。
- 最后一个参数是运行函数的参数;如果有多个参数,可将参数放入结构体中并将地址作为arg传入。
返回值:0 - 成功,-1 - 失败
pthread_attr_t结构体
typedef struct
{
int detachstate; 线程的分离状态
int schedpolicy; 线程调度策略
struct sched_param schedparam; 线程的调度参数
int inheritsched; 线程的继承性
int scope; 线程的作用域
size_t guardsize; 线程栈末尾的警戒缓冲区大小
int stackaddr_set;
void * stackaddr; 线程栈的位置
size_t stacksize; 线程栈的大小
}pthread_attr_t;
/*一般情况下,对于这些属性,我们会设置线程的分离状态,一般设置为分离态。
如果有需要的话,还会设置线程栈的大小。*/
int pthread_join(pthread_t thread, void **retval);
功能:以阻塞的方式等待thread指定的线程结束。当函数返回时,被等待线程的资源被收回。如果线程已经结束,那么该函数会立即返回。并且thread指定的线程必须是joinable的(pthread_create函数的第二个参数为NULL时)。
参数:
- thread: 线程标识符,即线程ID,标识唯一线程。
- retval: 用户定义的指针,用来存储被等待线程的返回值。
返回值: 0代表成功。 失败,返回的则是错误号。
void pthread_exit(void *retval)
功能:使用函数pthread_exit退出线程,这是线程退出的主动行为;
参数:一般情况下为NULL;retval是函数的返回代码,只要pthread_join中的第二个参数不是NULL,这个值将被传递给retval。
int pthread_attr_init(pthread_attr_t *attr);
功能:初始化一个线程属性对象
参数:attr 线程属性结构体指针变量
返回值:0 - 成功,非0 - 失败
int pthread_attr_destroy(pthread_attr_t *attr);
功能:销毁一个线程属性对象
参数:attr 线程属性结构体指针变量
返回值:0 - 成功,非0 - 失败
int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);
功能:线程栈大小设置
参数:attr 是线程属性变量;stacksize 则是设置的堆栈大小。
返回值:0 - 成功,非0 - 失败
int pthread_attr_getdetachstate(pthread_attr_t *attr,int detachstate);
功能:获取线程的分离状态属性
参数:attr 线程属性变量;detachstate 线程的分离状态属性。
返回值:0 - 成功,非0 - 失败
int pthread_attr_setdetachstate(pthread_attr_t *attr,int *detachstate);
功能:设置线程的分离状态属性
参数:attr 线程属性变量;detachstate 线程的分离状态属性。
返回值:0 - 成功,非0 - 失败
分离状态属性
- 可会合(joinable):这种关系下,主线程需要明确执行等待操作,在子线程结束后,主线程的等待操作执行完毕,子线程和主线程会合,这时主线程继续执行等待操作之后的下一步操作。主线程必须会合可会合的子线程。即使子线程能够在主线程之前执行完毕,进入终止态,也必须执行会合操作,否则,系统永远不会主动销毁线程,分配给该线程的系统资源也永远不会释放。
- 相分离(detached):表示子线程无需和主线程会合,也就是相分离的,这种情况下,子线程一旦进入终止状态,这种方式常用在线程数较多的情况下,有时让主线程逐个等待子线程结束,或者让主线程安排每个子线程结束的等待顺序,是很困难或不可能的,所以在并发子线程较多的情况下,这种方式也会经常使用。
代码示例:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>
void *thread_worker1(void *args);
void *thread_worker2(void *args);
int main(int argc, char *argv[])
{
int shared_var = 1000;
pthread_t tid;
pthread_attr_t thread_attr;
if(pthread_attr_init(&thread_attr))
{
printf("pthread_attr_init() failure: %s\n", strerror(errno));
return -1;
}
if(pthread_attr_setstacksize(&thread_attr, 20*1024))
{
printf("pthread_attr_setstacksize() failure: %s\n", strerror(errno));
return -1;
}
if(pthread_attr_setdetachstate(&thread_attr, PTHREAD_CREATE_DETACHED))
{
printf("pthread_attr_setdetachstate() failure: %s\n", strerror(errno));
return -1;
}
//创建第一个子线程,设置为相分离状态
pthread_create(&tid, &thread_attr, thread_worker1, &shared_var);
printf("Thread worker1 tid[%ld] created ok\n", tid);
//创建第二个子线程,设置为可会合状态
pthread_create(&tid, NULL, thread_worker2, &shared_var);
printf("Thread worker2 tid[%ld] created ok\n", tid);
pthread_attr_destroy(&thread_attr);
pthread_join(tid, NULL);
while(1)
{
printf("Control thread shared_var: %d\n", shared_var);
sleep(3);
}
}
void *thread_worker1(void *args)
{
int *ptr = (int *)args;
if(!args)
{
printf("%s() get invalid arguments\n", __FUNCTION__);
pthread_exit(NULL);
}
printf("Thread worker1 [%ld] start running...\n", pthread_self());
while(1)
{
printf("+++: %s before shared_var++: %d\n", __FUNCTION__, *ptr);
(*ptr)++;
sleep(3);
printf("+++: %s after shared_var++: %d\n", __FUNCTION__, *ptr);
}
printf("Thread worker1 exit...\n");
return NULL;
}
void *thread_worker2(void *args)
{
int *ptr = (int *)args;
if(!args)
{
printf("%s() get invalid arguments\n", __FUNCTION__);
pthread_exit(NULL);
}
printf("Thread worker2 [%ld] start running...\n", pthread_self());
while(1)
{
printf("---: %s before shared_var++: %d\n", __FUNCTION__, *ptr);
(*ptr)++;
sleep(3);
printf("---: %s after shared_var++: %d\n", __FUNCTION__, *ptr);
}
printf("Thread worker2 exit...\n");
return NULL;
}
运行结果及分析:
Thread worker1 tid[140250350417664] created ok
Thread worker1 [140250350417664] start running...
Thread worker2 tid[140250341795584] created ok
Thread worker2 [140250341795584] start running...
+++: thread_worker1 before shared_var++: 1000
---: thread_worker2 before shared_var++: 1001
+++: thread_worker1 after shared_var++: 1002
+++: thread_worker1 before shared_var++: 1002
---: thread_worker2 after shared_var++: 1002
---: thread_worker2 before shared_var++: 1003
+++: thread_worker1 after shared_var++: 1004
+++: thread_worker1 before shared_var++: 1004
/*
运行结果分析:
这里可以看到,最前面的thread_worker1子线程在对shared_var进行自加操作前后显示
它增加的值为2,这是为什么呢?
在创建两个线程时,我们都通过第四个参数将主线程栈中的 shared_var变量地址传给了
子线程,因为所有线程都是在同一进程空间中运行,而只是子线程有自己独立的栈空间,
所以这时所有子线程都可以访问主线程空间的shared_var变量。这也就是为什么子线程
thread_worker1在对shared_var自加一次后,值却增加了2的原因。
接下来我们看一下怎么解决这一个问题。
*/
2. 线程的互斥锁
互斥锁在线程中的作用
- 互斥锁是为了解决在多线程访问共享资源时,多个线程同时对共享资源操作产生的冲突而提出的一种解决方法。
- 在执行时,哪个线程持有互斥锁,并对共享资源进行加锁后,才能对共享资源进行操作,此时其它线程不能对共享资源进行操作。
- 只有在持有锁的线程将锁解锁释放后,其它线程才能进行抢锁加锁操作。
互斥锁的使用方法
-
在使用互斥锁之前,需要先创建一个互斥锁的对象。互斥锁的类型是pthread_mutex_t,所以定义一个变量就是创建了一个互斥锁。
pthread_mutex_t mutex; -
定义了一个互斥锁后,想使用这个互斥锁还是不行的,我们还需要对这个互斥锁进行初始化。
函数原型:int pthread_mutex_init(pthread_mutex_t *restrict_mutex,const pthread_mutexattr_t *restrict_attr);
//第二个参数是 NULL 的话,互斥锁的属性会设置为默认属性,默认属性为快速互斥锁;这里就不一一介绍锁的属性了。
pthread_mutex_init(&mutex, NULL); -
除了使用 pthread_mutex_init() 初始化一个互斥锁,我们还可以使用下面的方式定义一个互斥锁:
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
对互斥量加锁解锁的函数
-
阻塞锁
int pthread_mutex_lock(pthread_mutex_t *mutex);
如果这个锁此时正在被其它线程占用, 那么调用pthread_mutex_lock(&muetx);会进入到这个锁的排队队列中,并会进入阻塞状态, 直到拿到锁之后才会返回。 -
非阻塞锁
int pthread_mutex_trylock(pthread_mutex_t *mutex);
这个函数和pthread_mutex_lock()用法一样,只不过当请求的锁正在被占用的时候, 不会进入阻塞状态,而是立刻返回,并返回一个错误代码 EBUSY,意思是说, 有其它线程正在使用这个锁。
pthread_mutex_trylock(&muetx); -
释放互斥锁
int pthread_mutex_unlock(pthread_mutex_t *mutex);
用完了锁,一定要释放,不然下一个想要获得这个锁的线程, 只能一直等下去了,如果那个线程很不幸的使用了阻塞等待,那就悲催了。
pthread_mutex_unlock(&muetx);
示例代码:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>
typedef struct worker_plock_s
{
int shared_var;
pthread_mutex_t lock;
}worker_plock_t;
void *thread_worker1(void *args);
void *thread_worker2(void *args);
int main(int argc, char *argv[])
{
pthread_t tid;
pthread_attr_t thread_attr;
worker_plock_t worker_try;
worker_try.shared_var = 1000;
pthread_mutex_init(&worker_try.lock, NULL);
if(pthread_attr_init(&thread_attr))
{
printf("pthread_attr_init() failure: %s\n", strerror(errno));
return -1;
}
if(pthread_attr_setstacksize(&thread_attr, 20*1024))
{
printf("pthread_attr_setstacksize() failure: %s\n", strerror(errno));
return -1;
}
if(pthread_attr_setdetachstate(&thread_attr, PTHREAD_CREATE_DETACHED))
{
printf("pthread_attr_setdetachstate() failure: %s\n", strerror(errno));
return -1;
}
pthread_create(&tid, &thread_attr, thread_worker1, &worker_try);
printf("Thread worker1 tid[%ld] created ok\n", tid);
pthread_create(&tid, NULL, thread_worker2, &worker_try);
printf("Thread worker2 tid[%ld] created ok\n", tid);
pthread_attr_destroy(&thread_attr);
pthread_join(tid, NULL);
while(1)
{
printf("Control thread shared_var: %d\n", worker_try.shared_var);
sleep(5);
}
}
void *thread_worker1(void *args)
{
worker_plock_t *ptr = (worker_plock_t *)args;
if(!args)
{
printf("%s() get invalid arguments\n", __FUNCTION__);
pthread_exit(NULL);
}
printf("Thread worker1 [%ld] start running...\n", pthread_self());
while(1)
{ //阻塞锁
pthread_mutex_lock(&ptr->lock);
printf("+++: %s before shared_var++: %d\n", __FUNCTION__, ptr->shared_var);
(ptr->shared_var)++;
sleep(2);
printf("+++: %s after shared_var++: %d\n", __FUNCTION__, ptr->shared_var);
pthread_mutex_unlock(&ptr->lock);
sleep(2);
}
printf("Thread worker1 exit...\n");
return NULL;
}
void *thread_worker2(void *args)
{
worker_plock_t *ptr = (worker_plock_t *)args;
if(!args)
{
printf("%s() get invalid arguments\n", __FUNCTION__);
pthread_exit(NULL);
}
printf("Thread worker2 [%ld] start running...\n", pthread_self());
while(1)
{ //非阻塞锁
if(0 != pthread_mutex_trylock(&ptr->lock));
{
continue;
}
printf("---: %s before shared_var++: %d\n", __FUNCTION__, ptr->shared_var);
(ptr->shared_var)++;
sleep(2);
printf("---: %s after shared_var++: %d\n", __FUNCTION__, ptr->shared_var);
pthread_mutex_unlock(&ptr->lock);
sleep(2);
}
printf("Thread worker2 exit...\n");
return NULL;
}
运行结果及分析:
Thread worker1 tid[140214044743424] created ok
Thread worker1 [140214044743424] start running...
Thread worker2 [140214036121344] start running...
Thread worker2 tid[140214036121344] created ok
+++: thread_worker1 before shared_var++: 1000
+++: thread_worker1 after shared_var++: 1001
---: thread_worker2 before shared_var++: 1001
---: thread_worker2 after shared_var++: 1002
+++: thread_worker1 before shared_var++: 1002
+++: thread_worker1 after shared_var++: 1003
---: thread_worker2 before shared_var++: 1003
---: thread_worker2 after shared_var++: 1004
+++: thread_worker1 before shared_var++: 1004
/*
这次的运行结果与上面没有加互斥锁的对比一下,可以明显的看出两个子线程在
对shared_var的值进行修改时变得井然有序了起来,这都得益于互斥锁的存在。
*/
死锁
这里介绍一下死锁的概念,发生死锁时,进程永远不能完成,系统资源被阻碍使用,以致于阻止了其他作业开始执行。在讨论处理死锁问题的各种方法之前,我们首先深入讨论一下死锁特点。
必要条件
如果在一个系统中以下四个条件同时成立,那么就能引起死锁:
- 互斥:至少有一个资源必须处于非共享模式,即一次只有一个进程可使用。如果另一进程申请该资源,那么申请进程应等到该资源释放为止。
- 占有并等待:一个进程应占有至少一个资源,并等待另一个资源,而该资源为其他进程所占有。
- 非抢占:资源不能被抢占,即资源只能被进程在完成任务后自愿释放。
- 循环等待:有一组等待进程 {P0,P1,…,Pn},P0 等待的资源为 P1 占有,P1 等待的资源为 P2 占有,……,Pn-1 等待的资源为 Pn 占有,Pn 等待的资源为 P0 占有。
我们强调所有四个条件必须同时成立才会出现死锁。循环等待条件意味着占有并等待条件,这样四个条件并不完全独立。
避免死锁的方法
产生死锁需要四个条件,那么,只要这四个条件中至少有一个条件得不到满足,就不可能发生死锁了。由于互斥条件是非共享资源所必须的,不仅不能改变,还应加以保证,所以,主要是破坏产生死锁的其他三个条件。
- 破坏“占有且等待”条件
方法1:所有的进程在开始运行之前,必须一次性地申请其在整个运行过程中所需要的全部资源。
优点:简单易实施且安全。
缺点:因为某项资源不满足,进程无法启动,而其他已经满足了的资源也不会得到利用,严重降低了资源的利用率,造成资源浪费。使进程经常发生饥饿现象。
方法2:该方法是对第一种方法的改进,允许进程只获得运行初期需要的资源,便开始运行,在运行过程中逐步释放掉分配到的已经使用完毕的资源,然后再去请求新的资源。这样的话,资源的利用率会得到提高,也会减少进程的饥饿问题。 - 破坏“不可抢占”条件
当一个已经持有了一些资源的进程在提出新的资源请求没有得到满足时,它必须释放已经保持的所有资源,待以后需要使用的时候再重新申请。这就意味着进程已占有的资源会被短暂地释放或者说是被抢占了。
该种方法实现起来比较复杂,且代价也比较大。释放已经保持的资源很有可能会导致进程之前的工作实效等,反复的申请和释放资源会导致进程的执行被无限的推迟,这不仅会延长进程的周转周期,还会影响系统的吞吐量。 - 破坏“循环等待”条件
可以通过定义资源类型的线性顺序来预防,可将每个资源编号,当一个进程占有编号为i的资源时,那么它下一次申请资源只能申请编号大于i的资源。
3. socket多线程网络编程示例
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <fcntl.h>
#include <dirent.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/stat.h>
#include <errno.h>
#define PORT 7077
#define BACKLOG 100
#define BACK "Receive your ask!"
void *thread_worker1(void *args);
int main(int argc, char *argv[])
{
int listen_fd = -1;
int client_fd = -1;
int fd = -1;
int rv = -1;
struct sockaddr_in serv_addr;
struct sockaddr_in cli_addr;
socklen_t addrlen = 64;
char buf[1024];
char buf1[64];
int on = 1;
pthread_t tid;
pthread_attr_t thread_attr;
listen_fd = socket(AF_INET, SOCK_STREAM, 0);
if(listen_fd < 0)
{
printf("craete socket failure:%s\n",strerror(errno));
return -1;
}
printf("create socket[fd:%d] scuess\n",listen_fd);
setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(PORT);
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
if(bind(listen_fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr )) < 0)
{
printf("bind port failure:%s\n", strerror(errno));
return -1;
}
if(listen(listen_fd, BACKLOG) < 0)
{
printf("listen error:%s\n", strerror(errno));
return -1;
}
if(pthread_attr_init(&thread_attr))
{
printf("pthread_attr_init() failure: %s\n", strerror(errno));
return -1;
}
if(pthread_attr_setstacksize(&thread_attr, 20*1024))
{
printf("pthread_attr_setstacksize() failure: %s\n", strerr or(errno));
return -1;
}
if(pthread_attr_setdetachstate(&thread_attr, PTHREAD_CREATE_DETACHED))
{
printf("pthread_attr_setdetachstate() failure: %s\n", strerror(err no));
return -1;
}
while(1)
{
printf("\nwaiting new client connect...\n");
client_fd = accept(listen_fd, (struct sockaddr *)&cli_addr , &addrlen);
if(client_fd < 0)
{
printf("accept new socket failure:%s\n", strerror( errno));
return -1;
}
printf("Accept new socket[%s:%d] success!\n",inet_ntoa(cli _addr.sin_addr), ntohs(cli_addr.sin_port));
pthread_create(&tid, &thread_attr, thread_worker1, (void * )client_fd);
printf("Thread worker1 tid[%ld] created ok\n", tid);
}
close(listen_fd);
}
void *thread_worker1(void *args)
{
int c_fd = (int)args;
char buf[1024];
char buf1[64];
int fd = -1;
int rv = -1;
if(!args)
{
printf("%s() get invalid arguments\n", __FUNCTION__);
pthread_exit(NULL);
}
printf("Thread worker1 [%ld] start running...\n", pthread_self());
while(1)
{
memset(buf, 0, sizeof(buf));
if((rv = read(c_fd, buf, sizeof(buf))) < 0)
{
printf("read data from client socket failu re: %s\n",strerror(errno));
close(c_fd);
pthread_exit(NULL);
}
else if(rv == 0)
{
printf("client socket[%d] disconnected:\n" ,c_fd);
close(c_fd);
pthread_exit(NULL);
}
printf("read %d bytes data from client socket[%d]: %s\n", rv, c_fd, buf);
memset(buf1, 0, sizeof(buf1));
strncpy(buf1, BACK, strlen(BACK));
if(write(c_fd, buf1, strlen(buf1)) < 0 )
{
printf("write failure:%s\n",strerror(errno ));
close(c_fd);
pthread_exit(NULL);
}
}
printf("Thread worker1 exit...\n");
return NULL;
}
运行结果
create socket[fd:3] scuess
waiting new client connect...
Accept new socket[117.136.52.199:50311] success!
Thread worker1 tid[-1224903584] created ok
waiting new client connect...
Thread worker1 [-1224903584] start running...
Accept new socket[117.136.52.199:50312] success!
Thread worker1 tid[-1224928160] created ok
waiting new client connect...
Thread worker1 [-1224928160] start running...
Accept new socket[117.136.52.199:50313] success!
Thread worker1 tid[-1225178016] created ok
Thread worker1 [-1225178016] start running...
waiting new client connect...
read 5 bytes data from client socket[4]:hello
read 6 bytes data from client socket[5]:hahaha
read 5 bytes data from client socket[4]:hello
read 5 bytes data from client socket[6]:world
read 6 bytes data from client socket[5]:hahaha
read 5 bytes data from client socket[4]:hello
read 6 bytes data from client socket[5]:hahaha
read 5 bytes data from client socket[6]:world
read 5 bytes data from client socket[4]:hello
read 5 bytes data from client socket[6]:world