并发:是指在同一时刻,只能有一条指令执行,但多个进程指令被快速轮换执行,使得宏观上具有多个进程同时执行的效果
并行:是指在同一时刻,有多条指令在多个处理器上同时执行

pthread_self()用来获取线程id


主线程退出的话,其他的线程也会跟着退出,如果使用pthread_exit()则不会导致其他线程退出。
主线程打印奇数,子线程打印偶数,两个线程交替进行
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <string.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <pthread.h>
void *client_request_handler(void *param)
{
int i = 0;
while (1) {
i += 2;
printf("func:%s, line:%d, i:%d\n", __func__, __LINE__, i);
sleep(1);
}
return (void *)0;
}
int main(int argc, char *argv[])
{
pthread_t tl_Tid;
int l_iRet = 0;
int i = 1;
l_iRet = pthread_create(&tl_Tid, NULL, client_request_handler, &i);
if (l_iRet < 0) {
printf("can't create pthread\n");
return -1;
}
while (1) {
printf("func:%s, line:%d, i:0x%x\n", __func__, __LINE__, i);
i += 2;
sleep(1);
}
return 0;
}
线程的分离属性
- 创建一个线程默认是非分离的
- 分离和非分离的区别
2.1 如果线程具有分离属性,线程终止时,资源会被立刻回收
exit会导致整个进程退出

pthread_join()指定的线程必须是未分离的,如果是分离的就会失败。
join的返回值需要注意,他返回的值指向的还是join的那个线程的内部,容易出现问题,有可能那个变量被释放了或者更改
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <string.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <pthread.h>
void *client_request_handler1(void *param)
{
int i = 1;
printf("我是client_request_handler1, &i:0x%x\n", &i);
return (void *)(&i);
}
void *client_request_handler2(void *param)
{
pthread_detach(pthread_self());
return (void *)2;
}
int main(int argc, char *argv[])
{
pthread_t tl_Tid1;
pthread_t tl_Tid2;
int *lp_iVal1;
int *lp_iVal2;
int l_iRet = 0;
int i = 1;
l_iRet = pthread_create(&tl_Tid1, NULL, client_request_handler1, NULL);
if (l_iRet < 0) {
printf("can't create pthread\n");
return -1;
}
l_iRet = pthread_create(&tl_Tid2, NULL, client_request_handler2, NULL);
if (l_iRet < 0) {
printf("can't create pthread\n");
return -1;
}
//int pthread_join(pthread_t thread, void **retval);
//int pthread_detach(pthread_t thread);
l_iRet = pthread_join(tl_Tid1, &lp_iVal1);//如果线程已经分离,会join失败
printf("client_request_handler1 join的结果是:%d\n", l_iRet);
l_iRet = pthread_join(tl_Tid2, &lp_iVal2);
printf("client_request_handler2 join的结果是:%d\n", l_iRet);
printf("client_request_handler1 返回的结果是地址还是指针:0x%x\n", lp_iVal1);
printf("client_request_handler1 返回的结果是:%d\n", lp_iVal1);
printf("client_request_handler1 返回的结果是:%d\n", lp_iVal2);
printf("func:%s, line:%d, i:0x%x\n", __func__, __LINE__, i);
return 0;
}
输出的结果
sgy@ubuntu:~/sgy/user_program/pthread$ ./test
我是client_request_handler2
我是client_request_handler1, &i:0xb75d734c
client_request_handler1 join的结果是:0
client_request_handler2 join的结果是:22
client_request_handler1 返回的结果是地址还是指针:0xb75d734c
client_request_handler1 返回的结果是:-1218612404
client_request_handler1 返回的结果是:-1216454656
func:main, line:59, i:0x1
sgy@ubuntu:~/sgy/user_program/pthread$
tid1 join成功,tid2先detach成了分离状态,所以tid2,join会失败,而tid1的返回值得地址就是和tid1里面i的地址是一样的,指向线程内部的局部变量。
出错的number在哪里

线程的取消
pthread_cancel ---只是发送一个请求,并不意味着等待线程终止,而且成功也不一定意味着tid一定会终止
线程可以设置响不响应cancle信号,可以设置是立即响应还是延迟响应,默认是响应取消,延迟取消。
那到底延时到什么时候呢?
通过 man pthreads, 遇到取消点就会检查是否有取消信号。
Cancellation points
具体的函数说明
#include <pthread.h>
int pthread_setcancelstate( int state, int* oldstate );
描述:
pthread_setcancelstate() f函数设置线程取消状态为state,并且返回前一个取消点状态oldstate。
取消点有如下状态值:
PTHREAD_CANCEL_DISABLE:取消请求保持等待,默认值。
PTHREAD_CANCEL_ENABLE:取消请求依据取消类型执行;参考pthread_setcanceltype()。
参数:
state: 新取消状态。
oldstate: 指向本函数所存储的原取消状态的指针。
test.c
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <string.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <pthread.h>
void *client_request_handler1(void *param)
{
int l_iOldState = 0;
//int pthread_setcancelstate( int state, int* oldstate );
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &l_iOldState);
printf("我是子线程client_request_handler1\n");
sleep(4);//这个时候转过去执行main线程
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &l_iOldState);
printf("第1个取消点\n");
printf("第2个取消点\n");
return (void *)1;
}
int main(int argc, char *argv[])
{
pthread_t tl_Tid1;
pthread_t tl_Tid2;
int *lp_iVal1;
int *lp_iVal2;
int *lp_Rval;
int l_iRet = 0;
int i = 1;
l_iRet = pthread_create(&tl_Tid1, NULL, client_request_handler1, NULL);
if (l_iRet < 0) {
printf("can't create pthread\n");
return -1;
}
sleep(2);//去执行新线程
pthread_cancel(tl_Tid1);
pthread_join(tl_Tid1, &lp_Rval);
printf("pthread_join的返回值是:%d\n", (int *)lp_Rval);
return 0;
}
子线程里面将其取消状态设为了忽略取消,所以只有等到子线程将取消状态设为enable的时候,才会响应,在第一个printf的时候取消这个线程
pthread_kill()---信号发送函数
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <string.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <pthread.h>
#include <signal.h>
#include <errno.h>
void *client_request_handler1(void *param)
{
int l_iOldState = 0;
return (void *)1;
}
int main(int argc, char *argv[])
{
pthread_t tl_Tid1;
pthread_t tl_Tid2;
int *lp_iVal1;
int *lp_iVal2;
int *lp_Rval;
int l_iRet = 0;
int i = 1;
l_iRet = pthread_create(&tl_Tid1, NULL, client_request_handler1, NULL);
if (l_iRet < 0) {
printf("can't create pthread\n");
return -1;
}
sleep(2);
l_iRet = pthread_kill(tl_Tid1, 0);
if (ESRCH == l_iRet) {
printf("不存在这个一个线程\n");
}
return 0;
}
main线程sleep 2s中,子线程退出,此时发送信号,返回值肯定是表示线程不存在
线程的信号处理
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <string.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <pthread.h>
#include <signal.h>
#include <errno.h>
void pthread1_sig_handler(int arg)
{
printf("thread1 get signal\n");
return;
}
void pthread2_sig_handler(int arg)
{
printf("thread2 get signal\n");
return;
}
void *client_request_handler1(void *param)
{
printf("我是client_request_handler1\n");
//注册新号处理函数
struct sigaction l_stAct;
memset(&l_stAct, 0, sizeof(l_stAct));
sigaddset(&l_stAct.sa_mask, SIGQUIT);
l_stAct.sa_handler = pthread1_sig_handler;
sigaction(SIGQUIT, &l_stAct, NULL);
//线程内屏蔽掉这个信号,对这个信号不作处理
pthread_sigmask(SIG_BLOCK, &l_stAct.sa_mask, NULL);
sleep(2);//转回去执行主线程
return (void *)1;
}
void *client_request_handler2(void *param)
{
printf("我是client_request_handler2\n");
//注册新号处理函数
struct sigaction l_stAct;
memset(&l_stAct, 0, sizeof(l_stAct));
sigaddset(&l_stAct.sa_mask, SIGQUIT);
l_stAct.sa_handler = pthread2_sig_handler;
sigaction(SIGQUIT, &l_stAct, NULL);
//线程内屏蔽掉这个信号,对这个信号不作处理
//pthread_sigmask(SIG_BLOCK, &l_stAct.sa_mask, NULL);
sleep(2);//转回去执行主线程
return (void *)1;
}
int main(int argc, char *argv[])
{
pthread_t tl_Tid1;
pthread_t tl_Tid2;
int *lp_iVal1;
int *lp_iVal2;
int *lp_Rval;
int l_iRet = 0;
int i = 1;
l_iRet = pthread_create(&tl_Tid1, NULL, client_request_handler1, NULL);
if (l_iRet < 0) {
printf("can't create pthread\n");
return -1;
}
l_iRet = pthread_create(&tl_Tid2, NULL, client_request_handler2, NULL);
if (l_iRet < 0) {
printf("can't create pthread\n");
return -1;
}
sleep(1);
pthread_kill(tl_Tid1, SIGQUIT);
pthread_kill(tl_Tid2, SIGQUIT);
pthread_join(tl_Tid1, NULL);
pthread_join(tl_Tid2, NULL);
return 0;
}
如果是pthread2先执行,最终sigaction注册的是pthread1的信号处理函数,又因为pthread1 阻塞了信号,所以不会有打印,但是由于pthread2会对信号进行处理,所以会出现thread1 get signal
sgy@ubuntu:~/sgy/user_program/pthread$ ./test
我是client_request_handler2
我是client_request_handler1
thread1 get signal
sgy@ubuntu:~/sgy/user_program/pthread$
pthread_sigmask的那些参数的意思

线程清理函数
在下面三种情况下,pthread_cleanup_push()压栈的"清理函数"会被调用:
1, 线程调用pthread_exit()函数,而不是直接return.
2, 响应取消请求时,也就是有其它的线程对该线程调用pthread_cancel()函数。
3, 本线程调用pthread_cleanup_pop()函数,并且其参数非0
使用return 0;的时候并不会导致线程清理函数被调用
下面是示例代码
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <string.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <pthread.h>
#include <signal.h>
#include <errno.h>
/*
void pthread_cleanup_push(void (*routine)(void *),
void *arg);
*/
void cleanup1(void *arg)
{
printf("执行cleanup1\n");
return;
}
void cleanup2(void *arg)
{
printf("执行cleanup2\n");
return;
}
int main(int argc, char *argv[])
{
pthread_cleanup_push(cleanup1, NULL);
pthread_cleanup_push(cleanup2, NULL);
pthread_cleanup_pop(0);
pthread_cleanup_pop(0);
return 0;
}
运行结果
sgy@ubuntu:~/sgy/user_program/pthread$ ./test
sgy@ubuntu:~/sgy/user_program/pthread$
说明pthread_cleanup_pop(0);并不执行清理函数,那么是否会从栈上移走一个清理函数呢?
修改一下源代码
pthread_cleanup_push(cleanup1, NULL);
pthread_cleanup_push(cleanup2, NULL);
pthread_cleanup_pop(0);
pthread_cleanup_pop(1);
如果,pop(0)即便不执行,也会删除一个清理函数,那么下面一句pop(1)会执行cleanup2函数,看下面的运行结果
sgy@ubuntu:~/sgy/user_program/pthread$ ./test
执行cleanup1
sgy@ubuntu:~/sgy/user_program/pthread$
上面的运行结果说明了,pop(0),不会从栈上面删除线程清理函数
线程的同步
-
互斥量机制
1.1 互斥量变量定义
image.png

下面的示例代码演示了出现竞争的现象
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <string.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <pthread.h>
#include <signal.h>
#include <errno.h>
struct student {
int id;
int age;
}stu;
int i = 0;
void *client_request_handler1(void *param)
{
printf("我是client_request_handler1\n");
while (1) {
stu.age = i;
stu.id = i;
i++;
if (stu.age != stu.id) {
printf("出现了不同步的现象, func:%s, age:%d, id:%d\n", __func__, stu.age, stu.id);
break;
}
}
return (void *)1;
}
void *client_request_handler2(void *param)
{
printf("我是client_request_handler2\n");
while (1) {
stu.age = i;
stu.id = i;
i++;
if (stu.age != stu.id) {
printf("出现了不同步的现象, func:%s, age:%d, id:%d\n", __func__, stu.age, stu.id);
break;
}
}
return (void *)2;
}
int main(int argc, char *argv[])
{
pthread_t tl_Tid1;
pthread_t tl_Tid2;
int l_iRet = 0;
l_iRet = pthread_create(&tl_Tid1, NULL, client_request_handler1, NULL);
if (l_iRet < 0) {
printf("can't create pthread\n");
return -1;
}
l_iRet = pthread_create(&tl_Tid2, NULL, client_request_handler2, NULL);
if (l_iRet < 0) {
printf("can't create pthread\n");
return -1;
}
pthread_join(tl_Tid1, NULL);
pthread_join(tl_Tid2, NULL);
return 0;
}
理论上来讲,age和id永远都不可能会出现不相等的情况,但是由于线程间存在同步竞争,确实会出现这样的情况
sgy@ubuntu:~/sgy/user_program/pthread$ ./test
我是client_request_handler2
我是client_request_handler1
出现了不同步的现象, func:client_request_handler1, age:6070011, id:6070012
出现了不同步的现象, func:client_request_handler2, age:6070012, id:6070013
sgy@ubuntu:~/sgy/user_program/pthread$
怎么使用互斥量
pthread_mutex_t t_gMutex;//定义
pthread_mutex_init(&tg_Mutex, NULL);//初始化pthread_mutex_lock(&tg_Mutex);//加锁
pthread_mutex_unlock(&tg_Mutex);//解锁
读写锁
- 读状态加锁时,再有以写模式加锁的话,这个线程必须要等到所有的读锁释放,而且如果这个时候又来了读锁,会阻塞,防止写锁拿不到请求
- 写状态加锁时,如果有线程想要操作的话,所有的线程都会阻塞
条件变量
下面是一个使用条件变量的具体例子
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <string.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <pthread.h>
#include <signal.h>
#include <errno.h>
#define BUFFER_SIZE 3
#define PRODUCT_CNT 10
struct products {
int szBuffer[BUFFER_SIZE];
pthread_mutex_t t_Mutex;
int iReadPos;
int iWritePos;
pthread_cond_t t_HaveData;
pthread_cond_t t_NotFull;
}buffer_ops;
void init(struct products *p)
{
//初始化互斥量,保证对这个结构体的独占性
pthread_mutex_init(&p->t_Mutex, NULL);
pthread_cond_init(&p->t_HaveData, NULL);
pthread_cond_init(&p->t_NotFull, NULL);
p->iReadPos = 0;
p->iWritePos = 0;
}
void finish(struct products *p)
{
pthread_mutex_destroy(&p->t_Mutex);
pthread_cond_destroy(&p->t_HaveData);
pthread_cond_destroy(&p->t_NotFull);
p->iReadPos = 0;
p->iWritePos = 0;
}
void put(struct products *p, int data)
{
pthread_mutex_lock(&p->t_Mutex);
//判断是不是满的状态,下一个写的位置就是读,说明是满
if ((p->iWritePos + 1)% BUFFER_SIZE == p->iReadPos) {
printf("已经满了,等待非满的状态\n");
pthread_cond_wait(&p->t_NotFull, &p->t_Mutex);
}
p->szBuffer[p->iWritePos] = data;
p->iWritePos++;
if (p->iWritePos >= BUFFER_SIZE) {
p->iWritePos = 0;
}
//说明有数据了,应该提示消费者线程
pthread_cond_signal(&p->t_HaveData);
pthread_mutex_unlock(&p->t_Mutex);
}
int get(struct products *p)
{
int num = 0;
pthread_mutex_lock(&p->t_Mutex);
//为空
if (p->iReadPos == p->iWritePos) {
printf("现在是空的,等待数据\n");
pthread_cond_wait(&p->t_HaveData, &p->t_Mutex);
}
num = p->szBuffer[p->iReadPos];
p->iReadPos++;
if (p->iReadPos >= BUFFER_SIZE) {
p->iReadPos = 0;
}
//读出去一个说明没有满
pthread_cond_signal(&p->t_NotFull);
pthread_mutex_unlock(&p->t_Mutex);
return num;
}
void *producer(void *param)
{
printf("我是生产者\n");
int i = 0;
//
for (i = 0; i < 10; i++) {
sleep(1);
put(&buffer_ops, i);
printf("生产%d成功, 现在的读位置:%d, 写位置:%d\n", i, buffer_ops.iReadPos, buffer_ops.iWritePos);
}
printf("生产线程准备退出!\n");
return (void *)1;
}
void *consumer(void *param)
{
printf("我是消费者\n");
int num = 0;
static int cnt = 0;
while (1) {
sleep(2);
num = get(&buffer_ops);
printf("消费的数据是:%d, 现在的读位置:%d, 写位置:%d\n", num, buffer_ops.iReadPos, buffer_ops.iWritePos);
if (PRODUCT_CNT == ++cnt) {
break;
}
}
printf("消费者准备好退出了!\n");
return (void *)2;
}
int main(int argc, char *argv[])
{
pthread_t tl_Tid1;
pthread_t tl_Tid2;
int l_iRet = 0;
l_iRet = pthread_create(&tl_Tid1, NULL, producer, NULL);
if (l_iRet < 0) {
printf("can't create pthread\n");
return -1;
}
l_iRet = pthread_create(&tl_Tid2, NULL, consumer, NULL);
if (l_iRet < 0) {
printf("can't create pthread\n");
return -1;
}
pthread_join(tl_Tid1, NULL);
pthread_join(tl_Tid2, NULL);
finish(&buffer_ops);
return 0;
}
程序的运行结果
sgy@ubuntu:~/sgy/user_program/pthread$ ./test
我是消费者
我是生产者
生产0成功, 现在的读位置:0, 写位置:1
消费的数据是:0, 现在的读位置:1, 写位置:1
生产1成功, 现在的读位置:1, 写位置:2
生产2成功, 现在的读位置:1, 写位置:0
消费的数据是:1, 现在的读位置:2, 写位置:0
生产3成功, 现在的读位置:2, 写位置:1
已经满了,等待非满的状态
消费的数据是:2, 现在的读位置:0, 写位置:1
生产4成功, 现在的读位置:0, 写位置:2
已经满了,等待非满的状态
消费的数据是:3, 现在的读位置:1, 写位置:2
生产5成功, 现在的读位置:1, 写位置:0
已经满了,等待非满的状态
消费的数据是:4, 现在的读位置:2, 写位置:0
生产6成功, 现在的读位置:2, 写位置:1
已经满了,等待非满的状态
消费的数据是:5, 现在的读位置:0, 写位置:1
生产7成功, 现在的读位置:0, 写位置:2
已经满了,等待非满的状态
消费的数据是:6, 现在的读位置:1, 写位置:2
生产8成功, 现在的读位置:1, 写位置:0
已经满了,等待非满的状态
消费的数据是:7, 现在的读位置:2, 写位置:0
生产9成功, 现在的读位置:2, 写位置:1
生产线程准备退出!
消费的数据是:8, 现在的读位置:0, 写位置:1
消费的数据是:9, 现在的读位置:1, 写位置:1
消费者准备好退出了!
sgy@ubuntu:~/sgy/user_program/pthread$
一次性初始化
有的变量或者结构体只能初始化一次
pthread_once

6074_线程的分离属性
6075_线程栈属性
6076_线程同步属性
6077_线程私有数据
线程与fork
父进程锁住了互斥量,这个时候创建子进程的时候继承过来的
Linux系统下可以通过procfs或sysctl命令来查看pid_max的值:
cat /proc/sys/kernel/pid_max
或者
sgy@ubuntu:~$ sysctl kernel.pid_max
kernel.pid_max = 32768
sgy@ubuntu:~$
其实, 此上限值是可以调整的, 系统管理员可以通过如下方法来修改此上限值:
root@manu-rush:~# sysctl -w kernel.pid_max=4194304
kernel.pid_max = 4194304
进程组和会话
进程组和会话在进程之间形成了两级的层次: 进程组是一组相关进程的集合, 会话是一组相关进程组的集合。 用人来打比方, 会话如同一个公司, 进程组如同公司里的部门, 进程则如同部门里的员工。 尽管每个员工都有父亲, 但是不影响员工同时属于某个公司中的某个部门
可以调用如下指令来查看所有进程的层次关系:
ps -ejH
ps axjf
新进程默认继承父进程的进程组ID和会话ID
#include <unistd.h>
int setpgid(pid_t pid, pid_t pgid);
那么setpgid函数会将一个进程从原来所属的进程组迁移到pgid对应的进程组
当前进程的pid号是20253
sgy@ubuntu:~/sgy/user_program/aiworld_server$ ps
PID TTY TIME CMD
20523 pts/27 00:00:00 bash
21221 pts/27 00:00:00 ps
另外开一个终端来跟踪这个终端运行的情况
sgy@ubuntu:~$ sudo strace -f -p 20523
manu@manu-hacks:~$ setsid sleep 100
manu@manu-hacks:~$ ps ajxf
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND…
1 4469 4469 4469 ? -1 Ss 1000 0:00 sleep 100
网友评论