c线程操作

First Post:

Last Update:

Word Count:
3.1k

Read Time:
13 min

Thread

概念

线程: 有时候又称为轻量级进程, 程序执行的最小单位, 系统独立调度和分配cpu的最小基本单位, 他是进程中的一个实体. 一个进程中可以有多个线程,这些线程共享进程的所有资源,线程本身只包含一点必不可少的资源。

进程退出出现了很多弊端, 一是由于进程是资源的拥有者,创建,撤销与切换存在较大的时空开销,因此需要引入轻型进程, 而是由于对称多处理(smp)出现,可以满足多个运行单位,而多个进程并行开销过大。

并发

并发是指同一时刻,只能有一条指令执行, 但是多个进程指令被快速轮换执行,使得在宏观上具有多个进程同时执行的效果,看起来同时发生,单核

并行

并行是指在同一时刻,有多条指令在多处理器上同时执行,真正的同时发生

同步

彼此依赖关系的调用不应该”同时发生“,而同步就是要阻止那些”同时发生“的事情(比如数据库操作需要)

异步

异步的概念和同步是相对的,任何两个彼此独立的操作是异步的,它表明独立的发生

多线程的优势

​ 1 在多处理器开发程序的并行性

​ 2 在等待慢速IO操作时,程序可以执行其他操作,提高并发性

​ 3 模块化的编程,能更清晰的表达程序中独立事件的关系,结构清晰

​ 4 占用较少的系统资源

(注:多线程不一定要多核处理器)

线程的生命周期

创建线程

​ 线程 | 进程

标识符类型 pthread_t pid_t

获取id pthread_self() getpid()

创建 pthread_create() fork()

pthread_t: 结构体(FreeBSD5.2, Mac Os10.3) / unsinged long int (linux /usr/include/bits/pthreadtypes.h中定义)

#include <pthread.h> 

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, 
          void *(*start_routine) (void *), void *arg); 
     arg1: 传入储存thread_id的地址 
     arg2: 属性, 为NULL时为默认属性 
     arg3: 将要运行的函数地址(要为静态函数) 
     arg4: 传入的参数 
     If attr is NULL, then the thread is created with default attributes.

初始线程/主线程

  1. 在c程序运行时,首先运行main函数。在线程代码中,这个特殊的执行流被称作初始线程或者主线程。
  2. 住线程的特殊性在于,它在main函数返回的时候,会导致进程结束,进程内人所有的线程也会结束。这可不是一个好的现象,可以在主线程中调用pthread_exit函数,这样进程就会等待所有线程结束时才会终止。
  3. 主线程接收参数的方式是argc和argv,而普通的线程只有void*
  4. 在绝大多数情况下,主线程在默认堆栈上运行,这个堆栈可以增长到足够的长度,而普通线程的堆栈是受限制的,一旦溢出就会产生错误

创建线程

  1. 主线程是随着进程的创建而创建的
  2. 其他线程可以通过调用函数来创建,主要调用pthread_create
  3. 注意,新线程可能在当前线程的pthread_create函数返回之前就已经运行了,甚至可能运行完毕

线程的四个基本状态

就绪

线程能够运行,但是在等待可用的处理器

当线程刚被创建的时候就处于就绪状态, 或者当线程被解除阻塞以后也会处于就绪状态。就绪的线程在等待一个可用的处理器,当一个运行的线程被抢占时,它立刻又返回就绪状态

运行

线程在运行中,在多核系统中,可能同时有多个线程在运行。

当处理器选择一个就绪的线程执行时,它立刻变为运行状态

阻塞

线程在等待处理器中以外的其他条件

线程会在以下情况下发生阻塞: 试图加锁一个已经被锁住的互斥量,等待条件变量,调用singwait等待尚未发生的信号,执行无法完成的I/O信号,由于内存页错误

终止

线程从启动函数中返回, 或者调用ptrehad_exit函数,或则被取消

线程通常启动函数中返回终止自己,或者调用pthread_exit退出,或者取消线程

回收

线程的分离属性:

分离一个正在运行的线程并不影响它,仅仅是通知当前系统该线程结束时,其所属的资源可以回收。一个没有分离的线程在终止时会保留它的虚拟内存, 包括他们的堆栈和其他系统资源, 有时这种线程被称为“僵尸线程”。创建线程时默认是非分离的

如果线程具有分离属性,线程终止时会被立刻回收,但是你必须释放由该线程占有的程序资源。有malloc或者mmap分配的虚拟内存可以在任何时候由任何线程释放, 条件变量,互斥量,信号灯可以由任何线程销毁,只要他们被解锁了或者没有线程等待。但是只有互斥量的主人才能解锁它,所以在线程终止前,你需要解锁互斥量。

线程的基本控制

  1. 终止
  2. 连接
  3. 退出
  4. 清理

线程的终止

exit是危险的

如果进程中任意一个线程调用了eixt, _Exit, _exit, 那么整个进程就会终止

终止进程的方式

普通的单个线程有以下3中方式退出, 这样不会终止进程

  1. 从启动历程中返回, 返回值是线程的退出码
  2. 线程可以被同一进程中的其他线程取消
  3. 线程调用pthread_exit(void *rval)函数, rval是退出码

return 和 pthread_exit()的区别

线程的连接

1
2
3
4
5
   #include <pthread.h>
int pthread_join(pthread_t thread, void **retval);
//arg1: 指定线程的id
//arg2: 返回码, 如果线程被取消,那么被置为PHTREAD_CANCELED
//该函数成功调用返回0, 失败返回错误码

调用该函数的线程会一直阻塞,直到指定的线程tid调用pthread_exit, 从启动历程返回或者被取消

调用pthread_join会使指定的线程处于分离状态,如果制定线程已经处于分离状态,那么调用就会失败

phtread_detach可以分离一个线程, 一个线程被成功join后,其他线程就不能调用pthread_join连接指定的tid线程了.

例子

获取线程id

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int main(void) {
pid_t pid;
pthread_t tid;
//获取进程id
pid = getpid();

//获取线程id
tid = pthread_self();

printf("pid: %x pthread_id: %lx\n", pid, tid);
return 0;
}

创建线程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include <pthread.h>
#include <unistd.h>
#include <stdio.h>
/*
#include <pthread.h>

int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
arg1: 传入储存thread_id的地址
arg2: 属性, 为NULL时为默认属性
arg3: 将要运行的函数地址(要为静态函数)
arg4: 传入的参数
If attr is NULL, then the thread is created with default attributes.
*/

void print_id(char *arg) {
pid_t pid;
pthread_t tid;
pid = getpid();
tid = pthread_self();
printf("%s pid: %lx tid: %lx\n",arg, pid, tid);
}

void *thread_fun(void *arg) {
print_id(arg);
int a;
printf("\n stack: %p\n", &a);
return (void*)0;
}


int main(void) {
pthread_t ntid;
int err;
err = pthread_create(&ntid, NULL, thread_fun, "new thread");
if(err != 0) {
printf("create new thread failed\n");
return -1;
}
print_id("main_thread");
int a;
printf("\n stack: %p\n", &a);
sleep(2);

return 0;
}

实现创建线程传入多个参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <pthread.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

struct Student {
int age;
char name[64];
};

void *thread_fun(void *std) {
printf("age: %d name %s\n", ((struct Student *)std)->age, ((struct Student *)std)->name);
return (void*)0;
}

int main(int argc, char **argv) {

if(argc > 1) {
if(!strcmp(argv[1], "exit")) {
return 0;
}
}
struct Student std;
std.age = 16;
strcpy(std.name, "Hello World");
pthread_t tid;
int err;
err = pthread_create(&tid, NULL, thread_fun, &std);
if(err != 0) {
printf("create trhead fail!\n");
return -1;
}
int retval;

pthread_exit(&retval); //保证子线程能够顺利执行
printf("main thread exit\n");

return 0;
}

采用变量来实现同步

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#include <stdio.h>
#include <pthread.h>
#include <errno.h>

struct Value {
int thread;
int value;
int lock;
};

void *fun(void *arg) {
struct Value *value = (struct Value *)arg;

while(value->value < 50) {
if(value->lock == 0 && value->thread == 1) {
value->lock = 1;
printf("child: %d\n", value->value);
value->value += 1;
value->thread = 0;
value->lock = 0;
}
}

return (void*)0;
}

int main(void) {
struct Value value;
value.value = 0;
value.lock = 0;
value.thread = 0;

int err, retval;
pthread_t tid;
err = pthread_create(&tid, NULL, fun, &value);
if(err != 0) {
perror("thread_create:");
return -1;
}
while(value.value < 50) {
if(value.lock == 0 && value.thread == 0) {
value.lock = 1;
printf("main_thread: %d\n", value.value);
value.value += 1;
value.thread = 1;
value.lock = 0;
}
}

pthread_exit(0);
return 0;
}

验证线程的退出方式

3种退出方式,查看退出进程的返回值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <pthread.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

void *thread_fun(void *arg) {
if(!strcmp("1", (char *)arg)) {
printf("new thread return \n");
return (void *)1;
}else if(!strcmp("2", (char *)arg)) {
printf("new thread pthread_exit \n");
pthread_exit((void*)2);
}else {
printf("new thread exit\n");
exit(3);
}
}

int main(int argc, char **argv) {
int err;
pthread_t tid;
if(argc < 2) {
printf("input arg\n");
return 0;
}
err = pthread_create(&tid, NULL, thread_fun, argv[1]);
if(err != 0) {
printf("create new thread failed\n");
return 0;
}
sleep(1);

printf("main thread\n");
return 0;
}

通过运行时捕获线程状态。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

void *thread_fun_1() {
printf("I'm thread 1\n");
return (void*)1;
}

void* thread_fun_2() {
printf("I'm thread 2\n");
//pthread_detach(pthread_self()); //分离线程,会导致pthread_join失败
pthread_exit((void *)2);

return (void*)2;
}


int main(void) {
int err_1, err_2;
pthread_t tid_1, tid_2;
void *rval_1, *rval_2;

err_1 = pthread_create(&tid_1, NULL, thread_fun_1, NULL);
err_2 = pthread_create(&tid_2, NULL, thread_fun_2, NULL);
if(err_1 || err_2) {
printf("create new thread failed!\n");
return 0;
}
printf("I'm main thread\n");
printf("join1 rval is %d\n", pthread_join(tid_1, &rval_1));
printf("join2 rval is %d\n", pthread_join(tid_2, &rval_2));

printf("thread 1 exit code is %d\n", rval_1);
printf("thread 2 exit code is %d\n", rval_2);
printf("I'm main thread\n");
}

信号量

信号量
#include <semaphore.h>
采用信号量实现同步机制

1
int sem_init(sem_t *sem, int pshared, unsigned int val);

成功返回0, 失败返回EOF
sem: 为指定要初始化的信号两对象
pshared:为0时代表线程之间通信, 1代表进程间通信
val 信号量初值

信号量 P / V 操作

1
2
int sem_wait(sem_t *sem); //P操作, 申请资源, 可能会发生阻塞
int sem_post(sem_t *sem); //V操作, 释放资源, 不会发生阻塞

成功时返回0, 失败时返回EOF
sem指向要操作的信号量对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <semaphore.h> //使用信号量
#include <pthread.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
char buf[512];
sem_t sem;
void *thread_fun(void *arg) {
while(1) {
sem_wait(&sem);
printf("Your input: %s\n", buf);
}
}

int main(void) {
pthread_t tid;
if(sem_init(&sem, 0, 0) != 0) {
perror("sem_init(): ");
return -1;
}

if(pthread_create(&tid, NULL, thread_fun, NULL) != 0) {
perror("pthread_create(): ");
return -1;
}

printf("Input something, 'quit' to exit program\n");
do {
fgets(buf, 512, stdin);
sem_post(&sem);
}while(strncmp(buf, "quit", 4) != 0);

return 0;
}

严格实现同步

分别定义读和写的信号量来实现各种操作的严格同步。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <semaphore.h> //使用信号量
#include <pthread.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>

char buf[512];
sem_t sem_r, sem_w;
void *thread_fun(void *arg) {
while(1) {
sem_wait(&sem_r);
printf("Your input: %s\n", buf);
sem_post(&sem_w);
}
}

int main(void) {
pthread_t tid;
if(sem_init(&sem_r, 0, 0) != 0) {
perror("sem_init(): ");
return -1;
}

if(sem_init(&sem_w, 0, 0) != 0) {
perror("sem_init(): ");
return -1;
}

if(pthread_create(&tid, NULL, thread_fun, NULL) != 0) {
perror("pthread_create(): ");
return -1;
}

printf("Input something, 'quit' to exit program\n");
do {
fgets(buf, 512, stdin);
sem_post(&sem_r);
sem_wait(&sem_w);
}while(strncmp(buf, "quit", 4) != 0);

return 0;
}

打赏点小钱
支付宝 | Alipay
微信 | WeChat